9

If the generic type argument (of either a calling class or calling method) is constrained with where T : Base the new method in T == Derived is not called, instead the method in Base is called.

Why is the type T ignored for method call even though it should be known before run time?

Update: BUT, when the constraint is using an interface like where T : IBase the method in Base class is called (not the method in interface, which is also impossible).
So that means the system actually is able to detect the types that far and go beyond the type constraint! Then why doesn't it go beyond the type constraint in case of class-typed constraint?
Does that mean that the method in Base class that implements the interface has implicit override keyword for the method?

Test code:

public interface IBase
{
    void Method();
}

public class Base : IBase 
{
    public void Method()
    {

    }
}

public class Derived : Base
{
    public int i = 0;

    public new void Method()
    {
        i++;
    }
}

public class Generic<T>
    where T : Base
{
    public void CallMethod(T obj)
    {
        obj.Method();  //calls Base.Method()
    }

    public void CallMethod2<T2>(T2 obj)
        where T2 : T
    {
        obj.Method();  //calls Base.Method()
    }
}

public class GenericWithInterfaceConstraint<T>
    where T : IBase
{
    public void CallMethod(T obj)
    {
        obj.Method();  //calls Base.Method()
    }

    public void CallMethod2<T2>(T2 obj)
        where T2 : T
    {
        obj.Method();  //calls Base.Method()
    }
}

public class NonGeneric
{
    public void CallMethod(Derived obj)
    {
        obj.Method();  //calls Derived.Method()
    }

    public void CallMethod2<T>(T obj)
        where T : Base
    {
        obj.Method();  //calls Base.Method()
    }

    public void CallMethod3<T>(T obj)
        where T : IBase
    {
        obj.Method();  //calls Base.Method()
    }
}

public class NewMethod
{
    unsafe static void Main(string[] args)
    {
        Generic<Derived> genericObj = new Generic<Derived>();
        GenericWithInterfaceConstraint<Derived> genericObj2 = new GenericWithInterfaceConstraint<Derived>();
        NonGeneric nonGenericObj = new NonGeneric();
        Derived obj = new Derived();

        genericObj.CallMethod(obj);  //calls Base.Method()
        Console.WriteLine(obj.i);

        genericObj.CallMethod2(obj);  //calls Base.Method()
        Console.WriteLine(obj.i);

        genericObj2.CallMethod(obj);  //calls Base.Method()
        Console.WriteLine(obj.i);

        genericObj2.CallMethod2(obj);  //calls Base.Method()
        Console.WriteLine(obj.i);

        nonGenericObj.CallMethod(obj);  //calls Derived.Method()
        Console.WriteLine(obj.i);

        nonGenericObj.CallMethod2(obj);  //calls Base.Method()
        Console.WriteLine(obj.i);

        nonGenericObj.CallMethod3(obj);  //calls Base.Method()
        Console.WriteLine(obj.i);

        obj.Method();  //calls Derived.Method()
        Console.WriteLine(obj.i);
    }
}

Output:

0
0
0
0
1
1
1
2
Roland Pihlakas
  • 4,246
  • 2
  • 43
  • 64
  • What makes you think the interface-constrained one isn't calling `IBase.Method`? Try casting `Obj` to `IBase` and calling `Method` on it. – supercat May 24 '12 at 16:38
  • 1
    Note that it makes a difference if you write instead `public class Derived : Base, IBase`, i.e. repeat the interface in the declaration of the derived class as well. That is called *interface re-implementation*. [See the spec](https://msdn.microsoft.com/en-us/library/aa664594.aspx). – Jeppe Stig Nielsen Feb 25 '15 at 19:08

4 Answers4

7

Except when using dynamic objects, C# always binds methods at compile time--even when using generics. Virtual method calls are bound to virtual method slots rather than to the implementing methods, so that when they are performed on derived-class objects they will be directed to the derived-class implementations; although the methods to which the slots point will be determined at run time, the binding to the slots occurs at compile time. If a derived-class method is declared new rather than override, code which is bound using the derived class will use the derived-class method, but code which is bound using the base class will use the base-class method.

To understand why this has to be the case, imagine if it weren't. What should happen if class Base declares a method int Foo(), and a class Derived:Base declares a new string Foo(). If a generic class with constraint T:Base tries to call method Foo on an object of type T, what should the return type of that method be?

supercat
  • 77,689
  • 9
  • 166
  • 211
  • 2
    Good point with the return type. (You can go on about method binding and virtual calls and slots, but some of us need an obvious breaking example to figure it out.) – Rawling May 24 '12 at 16:07
  • @Rawling: The reason for discussing binding is to make clear that from the compiler's perspective, a generic type `T` constrained to `Foo` will in many ways behave more like a `Foo` than like the actual type that may be substituted for `T` at run-time. Generics in .net look like C++ templates, but they're fundamentally different. – supercat May 24 '12 at 16:35
  • @supercat: Thanks, I guess if there weren't the issue of return types of new methods then the design could have been different. This seems to me the **single** cause of this design decision. Everything else about binding and not overloading etc is just stating the consequence. I also should have stated more clearly in the question that I know about the existence of overloading. – Roland Pihlakas May 24 '12 at 17:13
  • @RolandPihlakas: I doubt that's the single cause, actually. Even if one were to formulate rules that would allow the compiler to resolve all potential ambiguities posed by issues like that, having generics behave as though they are run-time bound would require that every generic method be recompiled for every combination of generic types used therein. That would seriously harm performance, while offering comparatively little benefit. – supercat May 24 '12 at 17:59
  • @supercat: Yes, it would be inevitable that then multiple methods are being compiled for different generic types. But C++ does that too, isn't it? I was hoping that using generics enables me to get rid of virtual calls. Seems like this is not the case and the only performance-related use of generics is to avoid boxing value types. For classes, using generic argument types instead of interfaces (or base classes with virtual methods) can not offer performance benefits. – Roland Pihlakas May 24 '12 at 18:14
  • @RolandPihlakas: Using generics makes it possible to perform many types of type checks at compile time rather than run-time. For example, if code reads an item from a `IEnumerable`, it can use the item as an `Animal` without having to check at run-time whether it is or not. This both eliminates the need to have code check the type at run-time (which would take time) and also eliminates the possibility that a run-time type check might fail. – supercat May 24 '12 at 18:20
  • @RolandPihlakas: It's also important to note that for a C++ program to compile, the compiler must be able to identify all of the templated types that could ever be created during the execution of the program, and the size of the compiled program will grow proportional to the number of different combinations of types a method can use. In C#, even without using Reflection, one can write a program which would take an arbitrary-length string of digits (e.g. `24601`) and call a generic routine with a nested generic type (e.g. `X2>>>>`). Since the number of nested generic types... – supercat May 24 '12 at 18:36
  • ...such a routine could pass to the generic routine would vastly exceed the number of atoms in the universe, there would be no way the compiler could pre-generate code for them all. It would be possible for the JITter to compile the generic method once for each such type that's generated, but the performance cost would likely far exceed the benefit. – supercat May 24 '12 at 18:38
  • @supercat: Yes, I was hoping that JIT generates the generic methods/types as they are first initialized. Similarly to the one-time static type initialization. – Roland Pihlakas May 24 '12 at 18:58
  • @RolandPihlakas: Microsoft could have designed the JIT compiler that way, but it would have had a significant run-time performance cost, and there really aren't many situations where it would accomplish anything useful that couldn't be done better using a combination of compile-time binding of virtual functions and Reflection. Even though .net does not recompile generic code for every combination of class types it uses, generic classes hold a separate set of static variables for every combination of generic type parameters. – supercat May 24 '12 at 19:16
6

It is because T is constrained to have the semantics of Base. I can't tell you exactly what is going on with the type binding at runtime, but this is my educated guess.

You are not properly overriding the method, but instead hiding via "new", if you use a reference to the base class you bypass any hiding. This is where hiding falls down.

Members that hide other members are only honoured if you are using a reference to the type in which they are hidden. You can always bypass a hidden member by using a reference to the base class:

var derived = new Derived();
var baseRef = (Base)derived;
baseRef.Method(); // calls Base.Method instead of Derived.Method.

To properly override a method and have this code work, mark the method as virtual in the base class and override it in the derived class.

class Base
{
    public virtual void Method() {}
}

class Derived : Base
{
    public override void Method() {}
}

You can prove this, change your generic constraint to be where T : Derived and it should hit the "new" member.

Adam Houldsworth
  • 63,413
  • 11
  • 150
  • 187
0

That's due to the nature of the operator new: New unlike override, create a function with the same name as the base one, which mask the base method but doesn't override it.

Therefor, without a proper cast, the original method will be called if the reference is of type Base.

Samy Arous
  • 6,794
  • 13
  • 20
0

The new keyword simply hides the method instead of overloading it. The reason your non-generic CallMethod appears to work as expected is because the method signature expects a Derived instead of a Base.

Generics aren't really the culprit here. If you change the method signature to CallMethod(Base obj), you'll see the same "unexpected" behavior as the generic implementation and get the following output:

0
0
0
0
0
0
0
1

If you make Base.Method virtual and override it with Derived.Method like so:

public class Base 
{
    public virtual void Method()
    {

    }
}

public class Derived : Base
{
    public int i = 0;

    public override void Method()
    {
        i++;
    }
}

You'll get the following output:

1
2
3
4
5
6
7
8

Edit: updated to match question's updated output.

GWB
  • 2,575
  • 2
  • 23
  • 28