Avoid explicit type casting when overriding inherited methods
Asked Answered
B

5

11

I have a base abstract class that also implements a particular interface.

public interface IMovable<TEntity, T>
    where TEntity: class
    where T: struct
{
    TEntity Move(IMover<T> moverProvider);
}

public abstract class Animal : IMovable<Animal, int>
{
    ...

    public virtual Animal Move(IMover<int> moverProvider)
    {
        // performs movement using provided mover
    }
}

Then I have inherited classes some of which have to override interface implementation methods of the base class.

public class Snake : Animal
{
    ...

    public override Animal Move(IMover<int> moverProvider)
    {
        // perform different movement
    }
}

My interface methods return the same object instance after it's moved so I can use chaining or do something directly in return statement without using additional variables.

// I don't want this if methods would be void typed
var s = GetMySnake();
s.Move(provider);
return s;

// I don't want this either if at all possible
return (Snake)GetMySnake().Move(provider);

// I simply want this
return GetMySnake().Move(provider);

Question

As you can see in my example my overrides in child class returns base class type instead of running class. This may require me to cast results, which I'd like to avoid.

How can I define my interface and implementations so that my overrides will return the actual type of the executing instance?

public Snake Move(IMover<int> moverProvider) {}
Bub answered 28/5, 2014 at 11:58 Comment(2)
may be simpler re-organize classes and do something like: return provider.Move(GetMySnake()); in this case Move() will return the same object as a parameter and you can use generic approach if needed?Roadside
See Curiously Recurring Template Pattern and why you probably shouldn't do itCanaday
E
3

I suggest changing the return type of the interface method to void and moving the chaining behaviour to an extension method where you can get the real type of the target e.g.

public interface IMovable<TEntity, T>
    where TEntity : class
    where T : struct
{
    void MoveTo(IMover<T> moverProvider);
}

public abstract class Animal : IMovable<Animal, int>
{
    public virtual void MoveTo(IMover<int> mover) { }
}

public static class AnimalExtensions
{
    public static TAnimal Move<TAnimal>(this TAnimal animal, IMover<int> mover) where TAnimal : Animal, IMovable<TAnimal, int>
    {
        animal.MoveTo(mover);
        return animal;
    }
}

Note you can make the Move extension more generic if you need it to apply more generally:

public static TEntity Move<TEntity, T>(this TEntity entity, IMover<T> mover) where TEntity : IMovable<TEntity, T> where T : struct
{
    entity.MoveTo(mover);
    return entity;
}
Effy answered 28/5, 2014 at 12:12 Comment(4)
I have many different base abstract classes that would require extension methods then... Not really what I'm striving for...Bub
@RobertKoritnik That implies that you are able to write this once and re-use many times using some other solution. If you have many different abstract base classes, whatever solution you provide for one will need replicating anyway, unless they all derived from some master object, at which point I'd back away from the object hierarchy and rethink.Snodgrass
@RobertKoritnik - If you need to do this with more than animals you can make the Move extension method more general - see update. There's no general way to restrict a method's return type to the receiver's type, the closest you can get is the recurring template pattern. This has the drawback of adding a generic parameter to every type in the hierarchy and can quickly get complicated so I prefer moving the generics outside to another method as shown here. This can cause generic type inference to fail however.Effy
I now implemented your solution. I'm still having some problems but that will become a new question...Bub
V
2

You can convert Animal to a generic type that accepts the concrete type as a type parameter:

public abstract class Animal<T> : IMovable<T, int> where T:Animal<T>        
{


    public virtual T Move(IMover<int> moverProvider)
    {
    ...
    }
}

public class Snake : Animal<Snake>
{


    public override Snake Move(IMover<int> moverProvider)
    {
    ...
    }
}
Vicinage answered 28/5, 2014 at 12:6 Comment(17)
That allows for one layer, what happens to public class SideWinder: Snake ?Banebrudge
This is usually paired with the self-referencing generic constraint where T : Animal<T>.Snodgrass
@JamesBarrass The same, you have just stated that Snake is now a base class so can do Snake<T> : Animal<T>.Snodgrass
@AdamHouldsworth fixed. I was actually looking how to write that very thingVicinage
@AdamHouldsworth but now you have to add <T> to Snake's definition in order to inherit? surely that's not right to edit the base class to help define the derived class? What if you also want a new Snake? what <T> do you give it?Banebrudge
This is called the Curiously Recurring Template Pattern and is advised againstCanaday
@JamesBarrass this isn't about inheritance, it's a syntax quirk for defining generic classes.Vicinage
@JamesBarrass You are defining your model as a need to be able to specify specific snakes. In this case, the recurring template pattern becomes slightly more unsustainable than the original use case, there are only a very small handful of situations where it is useful. I tend to not use it. As for the non-generic Snake case, you can simply define a derived class that is UnknownSnake or GeneralSnake to inherit Snake<T>, though by this point I would be abandoning the self-referencing constraint.Snodgrass
@PanagiotisKanavos: you returned null in base abstract class method implementation. If I return this I need to cast it... :(Bub
@Canaday indeed, but it's probably the only way to answer the specific question. Most likely, the class hierarchy should be refactored to work in a better wayVicinage
@PanagiotisKanavos: What kind of refactoring would you suggest?Bub
@RobertKoritnik Simply so I can compile a sample.Vicinage
@PanagiotisKanavos: My comment didn't include a question which is: How can I also avoid casting when returning this which is what I'm doing in all of these as I want call chaining?Bub
@PanagiotisKanavos I agree, but I think it's important mention this pattern has its pitfalls for those that may not be aware of it.Canaday
@Canaday the article you point to actually highlights this particular use as one of the places where it solves the task at handAl
@RuneFS except that it's still possible to create a Snake : Animal<Cat> which is weird, and it's not a very pretty design to look at, still.Canaday
@PanagiotisKanavos: Another problem. What if I have an entity Person that has a property Pet which can be any animal. How do I set its type? I can't say Animal<Animal<>>? I'd need to provide these types as generic types on Person as well. And this may become rather complex if it has several different such typed properties... I would still like to know an alternative you implied earlier...Bub
O
1

How about:

public virtual T Move<T>(IMover<int> moverProvider) where T : Animal
{
    // performs movement using provided mover
}
Oquinn answered 28/5, 2014 at 12:8 Comment(0)
S
1

Sometimes you need to have current type as method return value and it has to change in derived classes. I'd avoid this pattern because it'll lead to strange behaviors and unusual syntax (if your model becomes complex) but give it a try (primary because for very small hierarchies it looks pretty simple):

abstract class Animal<TConcrete> : IMovable<TConcrete, int>
where TConcrete : Animal<T>
{
    public virtual T Move(IMover<int> moverProvider) {
        return (T)this; // Cast to Animal<T> to T isn't implicit
    }
}

sealed class Snake : Animal<Snake>
{
    public virtual Snake Move(IMover<int> moverProvider) {
        return this;
    }
}

Why is this bad? You can answer yourself when you'll need to declare a generic variable of type Animal<TConcrete> (in practice this stops you to have a variable with that base class).

What I'd do is to make this requirement clear (with a class or an extension method - in this case using another name):

abstract class Animal : IMovable<Animal, int>
{
    // Please note that this implementation is explicit
    Animal IMovable<Animal, int>.Move(IMover<int> moverProvider) {
        return MoveThisAnimal(moverProvider);
    }

    protected virtual Animal MoveThisAnimal(IMover<int> moverProvider) {
        // Peform moving
        return this;
    }
}

class Snake : Animal
{
    public Snake Move(IMover<int> moverProvider) {
        return (Snake)MoveThisAnimal(moverProvider);
    }

    protected override Animal MoveThisAnimal(IMover<int> moverProvider) {
        // Peform custom snake moving
        return this;
    }
}
Stead answered 28/5, 2014 at 12:20 Comment(0)
B
0

It's messy, but by introducing a non-generic base interface, an extension method can give the desired result. It can also be simplified (to remove the second explicit interface implementation) if you don't care about exposing the 'MoveFunc' to callers:

public interface IMovable
{
    IMovable MoveFunc();
}

public interface IMovable<TEntity, T> : IMovable
    where TEntity : IMovable
{
    new TEntity MoveFunc();
}

public abstract class Animal : IMovable<Animal, int>
{
    protected virtual Animal MoveFunc()
    {
        // performs movement using provided mover
        Debug.WriteLine("Animal");
    }

    Animal IMovable<Animal, int>.MoveFunc()
    {            
        return MoveFunc();
    }

    IMovable IMovable.MoveFunc()
    {
        return ((IMovable<Animal, int>)this).MoveFunc();
    }
}

public class Snake : Animal
{
    protected override Animal MoveFunc()
    {
         // performs movement using provided mover
         Debug.WriteLine("Snake");
    }
}

public static class IMovableExtensions
{
    public static TOut Move<TOut>(this TOut entity) where TOut : IMovable
    {
        return (TOut)entity.MoveFunc();
    }

}

...

Snake snake = new Snake();

Snake moved = snake.Move(); // "Snake"

Animal animal = snake;

animal.Move() // "Snake"
Bemire answered 28/5, 2014 at 21:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.