3

I ran into a interesting issue last week and I am not sure I truly understand the polymorphic nature of the following code. I created this sample based some code that I wrote.

Basic Setup

  • I have a class "Trainer" that knows how to train a animal.
  • I use that as a base class and created a subclass "DogTrainer" that knows how to only train dogs.
  • I created a function pointer using the superclass as the return type.
  • I then call that function getting a new instance of the subclass "DogTrainer".
  • I then call the "Train" method of the instance returned from the function pointer.
  • The "Train" method invokes the "Trainer" - "Train" method not the "DogTrainer" - "Train" method as expected

Here is the code

// Does not work as expected
// Calls the Trainer Train not the DogTrainer Train
var getTrainer = new Func<Trainer>(() => new DogTrainer(new Dog()));
var funcTrainer = getTrainer();
Console.WriteLine(funcTrainer.Train());

Now if I use a Interface as the return type it does work as expected "IF" I have the interface directly marked on the "DogTrainer" subclass

// Works as expected 
var getITrainer = new Func<ITrainer>(() => new DogTrainer(new Dog()));
var funcITrainer = getITrainer();
Console.WriteLine(funcITrainer.Train());

If I do not have the interface on the subclass it does not work as expected. See Example

// Does not work as expected 
// Calls the Trainer Train not the Dog Trainer Train
var getITrainerWithNoInterface = new Func<ITrainer>(() => new DogTrainerWithNoInterface(new Dog()));
var funcITrainerWithNoInterface = getITrainerWithNoInterface();
Console.WriteLine(funcITrainerWithNoInterface.Train());

If someone could let me know what I am missing here, this behavior is not what I expected. When I discovered this bug in my code I was able to solve it, what I am looking for here is the "Why" this is happening.

Here is DogTrainerWithNoInterface which is likely the main part of the puzzle (Trainer:ITrainer)

public class DogTrainerWithNoInterface : Trainer
{
    public DogTrainerWithNoInterface(IAnimal animal)
        : base(animal)
    { }

    public new string Train()
    {
        return $"Speak Ubu, Speak : {Animal.Speak()}";
    }
}
Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
Craig Selbert
  • 737
  • 1
  • 8
  • 17
  • 2
    You're seeing this behavior because of covariant generic types, not because of polymorphism – David L Dec 21 '15 at 19:53
  • 1
    This is the behavior of "interface reimplementation" as it is called in the language specification. I described it in a similar example [here](http://stackoverflow.com/a/25008171/517852). It is unrelated to `Func` which only complicates matters unnecessarily here. – Mike Zboray Dec 21 '15 at 19:57
  • Thanks for effort puting the question together with good compact samples. As @mikez pointed out large part of the question has very nice answer alread (linked as duplicate). The first sample is covered by [What is shadowing](http://stackoverflow.com/questions/673779/what-is-shadowing). – Alexei Levenkov Dec 21 '15 at 20:08
  • Thanks for the information. I see what is going on now. – Craig Selbert Dec 22 '15 at 14:23

1 Answers1

1

NOTE: This answer is purely for describing the "new" issue due to not fitting in the comment. Please ignore voting as it doesn't fully answer his question.

Sample from OP's link:

public class DogTrainer : Trainer, ITrainer
{
    public DogTrainer(IAnimal animal)
        : base(animal)
    { }

    public new string Train()
    {
        return $"Speak Ubu, Speak : {Animal.Speak()}";
    }
}

Do not use new when declaring methods/properties. Make them virtual on the base and then override them. What you are doing is hiding the method on your base.

You can only get your new method to invoke when working with that specific type (DogTrainer). Any down-casting to the base will invoke it's method i.e. working with a variable declared as Trainer or ITrainer.

Knowing When to Use Override and New Keywords (C# Programming Guide)

TyCobb
  • 8,909
  • 1
  • 33
  • 53
  • Note that you answering almost opposite of what OP observed - second version (interface based) obviously successfully calls `DogTrainer.Train` and not the base one. – Alexei Levenkov Dec 21 '15 at 19:59
  • True. Guess I should note that. Way too much to fit in the comment block. – TyCobb Dec 21 '15 at 20:00