C# & generics - why is method in base class called instead of new method in derived class?

一世执手 提交于 2019-12-04 05:32:36

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?

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.

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.

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!