Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Polymorphism not working for a call from a generic class in C#

It looks like in the following case the polymorphism does not work properly I have the following definitions:

interface BaseInterface{}
interface NewInterface:BaseInterface{}
class NewClass:NewInterface{}

class GenericClass<T> where T:BaseInterface
{
    public string WhoIAm(T anObject)
    {
        return TestPolymorphism.CheckInterface(anObject);
    }
}

class ImplementedClass:GenericClass<NewInterface>{}

class TestPolymorphism
{
    public static string CheckInterface(BaseInterface anInterface)
    {
        return "BaseInterface";
    }

    public static string CheckInterface(NewInterface anInterface)
    {
        return "NewInterface";
    }
}

Then when I call :

NewClass nc = new NewClass();
ImplementedClass impClass = new ImplementedClass();
Console.WriteLine("The result is " + impClass.WhoIAm(nc));

I have "The result is BaseInterface"

I was expecting to have "The result is NewInterface" as nc implement BaseClass and NewClass
What would be the best way to get "NewClass" as the result ?

Thanks

like image 559
Gutti Avatar asked May 30 '12 19:05

Gutti


1 Answers

Keep in mind with generic methods, that non-virtual method calls are still resolved at compile time of the generic itself, not at the compile time of the realization of the generic.

Thus this:

class GenericClass<T> where T:BaseInterface
{
    public string WhoIAm(T anObject)
    {
        return TestPolymorphism.CheckInterface(anObject);
    }
}

Will resolve to the overload that takes a BaseInterface because that's what you've constrained it to regardless of the type T actually is.

Generics in C# aren't quite like templates in C++, in C# generics, all reference types share the same code for the generic, thus they all treat the generic type similarly at compile time. This means that any compile time overloads your generic invokes using the generic type placeholder can only go by any constraints you provide on the generic type itself, since generics are compiled before they are actually realized.

That is, your GenericClass<T> is compiled before any usage of it is considered (which is very different from the way C++ does templates -- both methods have their pros and cons). So, if you have an unconstrained generic (say, just T) then it is considered object for the purposes of overloading (roughly speaking), but if you have a constrained generic (say where T : BaseInterface) then it is considered BaseInterface for the purposes of overloading.

You'd see something similar in this case:

public static bool UberEquals<T>(T left, T right) where T : class
{
    return left == right;
}

So you'd think if you called this by:

var s1 = "ello";
var s2 = "Hello";

UberEquals<string>('H' + s1, s2);

Since T is type string then it would call strings == overload, but it doesn't, because you haven't constrained T, thus at compile time it assumes a least common denominator of object and uses objects == instead.

Another way to think of this:

BaseInterface bi = new ImplementedClass();

var x = TestPolymorphism.CheckInterface(bi);

X will always say BaseInterface in the above, since overloads are resolved at compile time, not dynamically at run-time. Very similar to generics, just keep in mind that the generic is compiled before it is realized, so it can only go on whatever base class or interface you constrain it to for the purposes of overload resolution.

like image 133
James Michael Hare Avatar answered Oct 05 '22 15:10

James Michael Hare