3

I have quite simple class hierarchy:

public class Base
{
    //...
    public virtual void AssignFrom(Base baseObj)
    {
        //DoSomeStuff
    }
}
public class DerivedA : Base
{
    //...

    public override void AssignFrom(Base derivedObj)
    //Wish Method would be:
    //public override void AssignFrom(DerivedA derivedObj)
    {
        base.AssignFrom(derivedObj);
        var objAsDerivedA = derivedObj as DerivedA;
        //Some other stuff concerning derived class
    }
}
public class DerivedB : Base
    {
    //...

    public override void AssignFrom(Base derivedObj)
    //Wish Method would be:
    //public override void AssignFrom(DerivedB derivedObj)
    {
        base.AssignFrom(derivedObj);
        //Here I have to cast, what I would gladly avoid.
        var objAsDerivedB = derivedObj as DerivedB;
        //Some other stuff concerning derived class
    }
}

What bothers me is that each sibling of derived class or it's descendant can be used as parameter.

It would be possible to create own, non virtual method for each class, but in my opinion the association/connection between methods will be lost.

Question: Is there a way to restrict method's parameter to the class, where the method is overridden.

Rekshino
  • 141
  • 2
  • 8
  • There's always generics. not sure it makes things any nicer in this case though – Ewan Jul 19 '18 at 14:50
  • Generics is a possibility, but the idea was, that each class take carry about it's specific data and there is parent-child relation – Rekshino Jul 19 '18 at 14:52
  • 4
    @Rekshino it sounds like your design here is flawed. You're trying to use polymorphism where it isn't applicable because you care too much about the runtime types. Without knowing more than what you've posted here, it sounds like you need to replace some inheritance with some composition. – Ant P Jul 19 '18 at 14:55
  • @Rekshino What's your motivation for doing this? What *are* `Base`, `DerivedA` and `DerivedB` in reality? – Tanner Swett Jul 20 '18 at 00:30
  • @TannerSwett In reality they are configuration classes. Motivation is to have a method, wich should be overridden with restriction of parameter to the class, where the method be overriden. The solution from Ewan is quite interesting, take a look. – Rekshino Jul 20 '18 at 07:09
  • I plused "design ... flawed". Even given the accepted answer I read ".. a second layer fails." and I don't need to read further. We're about to violate Liskov principle maybe? I wonder - could`AssignFrom()` be virtual in the base, implemented in `DerivedA` and from there use that as a base - [the prototype pattern?](https://sourcemaking.com/design_patterns/prototype). – radarbob Jul 26 '18 at 19:39

6 Answers6

4

So you can do one level of inheritance with Generics. But a second layer fails essentially because you are attempting to remove the public AssignFrom method of the base class

public class Base
{
    public string MyProp { get; set; }
}

public class Base<T> : Base where T : Base
{
    //...
    public virtual void AssignFrom(T baseObj)
    {
        this.MyProp = baseObj.MyProp;
    }
}


public class DerivedA : Base<DerivedA>
{
    public string ExtraProp { get; set; }
    //...
    public override void AssignFrom(DerivedA derivedObj)
    {
        base.AssignFrom(baseObj);
        this.ExtraProp = baseObj.ExtraProp;
    }
}

public class DerivedB : Base<DerivedB>
{
    public override void AssignFrom(DerivedB derivedObj)
    {
        //this is fine
    }
}

public class DerivedB : DerivedA
{
    public override void AssignFrom(DerivedB derivedObj)
    {
        //this wont compile: no suitable method to override
    }
}

public class DerivedB : DerivedA, Base<DerivedB> //multiple inheritance not allowed!
{
    public override void AssignFrom(DerivedB derivedObj)
    {
    }
}

A generic interface can enforce the addition of an AssignFrom(DerivedB baseObj) method but not the removal of the base object's AssignFrom(DerivedA baseObj)

Essentially you would just be adding per class methods.

You will have to overcome your total addiction to Base.

The solution I feel is Copy Contstructors

public class Base
{
    public Base(Base baseObj) { }
}

public class DerivedA : Base
{
    public DerivedA(DerivedA derivedObj) : base(derivedObj) { }
}

public class DerivedB : DerivedA
{
    public DerivedB(DerivedB derivedObj) : base(derivedObj){ }
}

Now you can implement a constructor which sends something the the base class, but constructors are not inherited. So you don't have to remove the base classes constructor from your derived class

Ewan
  • 70,664
  • 5
  • 76
  • 161
3

I think it will help you.

You can use the Generic Type (T) to ensure the type of nested classes. In nested classes, include the type of it.

public class Base<T>
{
    public T BaseObj;
    //...
    public virtual void AssignFrom(T baseObj)
    {
        //DoSomeStuff
        BaseObj = baseObj;
    }
}
public class DerivedA : Base<DerivedA>
{
    //...

    public override void AssignFrom(DerivedA derivedObj)
    {
        base.AssignFrom(derivedObj);
        var objAsDerivedA = derivedObj;
        //Some other stuff concerning derived class
    }
}
public class DerivedB : Base<DerivedB>
{
    //...

    public override void AssignFrom(DerivedB derivedObj)
    { 
        base.AssignFrom(derivedObj);
        var objAsDerivedB = derivedObj;
        //Some other stuff concerning derived class
    }
}

I hope it been useful for you. Good code!

phduarte
  • 286
  • 1
  • 5
1

Specifically related to C#, the answer is no, for the foreseeable future.

What you are asking for are called covariant return types. There is a "championed" proposal on the csharplang language repo for this feature. This means the C# language team are prepared to at least discuss the idea. But it it isn't yet scheduled for any upcoming release of the language, assuming it ever happens.

David Arno
  • 38,972
  • 9
  • 88
  • 121
1

You cannot get this 100% type-safe in C♯, unfortunately. You would need a feature typically called "MyType" for that, which C♯ doesn't have. A MyType is essentially a type annotation that says "type of this object". So, for an instance of DerivedA, the MyType would be DerivedA, and so on. You can imagine it something like this:

public class Base
{
    //...
    public virtual void AssignFrom(typeof(this) baseObj)
    {
        //DoSomeStuff
    }
}

Scala has a this.type, but that wouldn't help you either, since it is a singleton type whose only instance is this. The only object you would be allowed to pass into that method, would be this which is of course stupid, since you are already passing this as the invisible zeroth argument anyway. (It is useful for return types, though, where it tells you that the method returns its receiver.) It would look something like this in Scala:

class Base {
  //...
  public assignFrom(baseObj: this.type) = {
    //DoSomeStuff
  }
}

The closest you can get is by using F-bounded Polymorphism, where you pass the extending class as a type argument:

public class Base<T> where T: Base<T>
{
    //...
    public virtual void AssignFrom(T baseObj)
    {
        //DoSomeStuff
    }
}
public class DerivedA : Base<DerivedA>
{
    //...

    public override void AssignFrom(DerivedA derivedObj)
    {
        base.AssignFrom(derivedObj);
        //Some other stuff concerning derived class
    }
}
public class DerivedB : Base<DerivedB>
    {
    //...

    public override void AssignFrom(DerivedB derivedObj)
    {
        base.AssignFrom(derivedObj);
        //Some other stuff concerning derived class
    }
}

Note, however, that this is not as type-safe as a "proper" MyType, since nobody stops a client from doing something like this:

public class Nonsense : Base<SomeTotallyUnrelatedClass>
    {
    //...

    public override void AssignFrom(SomeTotallyUnrelatedClass derivedObj)
    {
        base.AssignFrom(derivedObj);
        //Some other stuff concerning derived class
    }
}
Jörg W Mittag
  • 101,921
  • 24
  • 218
  • 318
1

You can merely introduce the desired methods, rather than overriding.  In proper context, the compiler will invoke the appropriate method.  (This still won't absolutely prevent Base.AssignFrom from being used, though.)

public class Base
{
    //...
    public virtual void AssignFrom(Base baseObj)
    {
        //DoSomeStuff
    }
}
public class DerivedA : Base
{
    //...

    public void AssignFrom(DerivedA derivedObj)
    {
        base.AssignFrom(derivedObj);
        var objAsDerivedA = derivedObj as DerivedA;
        //Some other stuff concerning derived class
    }
}
public class DerivedB : Base
    {
    //...

    public void AssignFrom(DerivedB derivedObj)
    {
        base.AssignFrom(derivedObj);
        //Here I have to cast, what I would gladly avoid.
        var objAsDerivedB = derivedObj as DerivedB;
        //Some other stuff concerning derived class
    }
}

FYI, you can also do both: the override and the newly introduced method:

public override void AssignFrom(Base obj) { }
public void AssignFrom(DerivedA derivedObj) { }
Erik Eidt
  • 33,282
  • 5
  • 57
  • 91
0

First, you can do this with dynamic testing - check the type of the argument in DerivedA::AssignFrom () - and throw if its not of the right type (or assert out).

But in a language like C#, this really doesn't make sense to do through static analysis.

Consider this code:

public Base SomeFactoryFunction()
{ 
     return random(0,1)%2=1? new DerviedA () : new DerviedB ();
}

...
public void some_other_code()
{
    Base b = new Base ();
    DerivedA da = new DerivedA ();
    DerivedB db = new DerivedB ();

    // should this compile? - Maybe no
    da.AssignFrom (SomeFactoryFunction());

    b = da;
    // should this compile?
    b.AssignFrom (SomeFactoryFunction());
 }

The key is - that in a language like C#, you practically never know the dynamic types of the underlying objects (statically).

The reason this works in C++ (there are cases where it doesn't there either) - but in C++ - you can often structure your code using classes so at compile time (statically) - the compiler knows the dynamic types of the objects, and so can perform this sort of checking (roughly the same code above that wont work in C# works in C++ if you lose the 'new' part- just using stack based objects).

Lewis Pringle
  • 2,935
  • 1
  • 9
  • 15