C#&泛型 - 为什么在派生类中调用基类中的方法而不是新方法?

时间:2012-05-24 15:53:14

标签: c# generics inheritance method-call method-overriding

如果泛型类型参数(调用类或调用方法)受where T : Base约束,则不会调用T == Derived中的新方法,而是调用Base中的方法。

为什么方法调用会忽略类型T,即使它应该在运行时之前知道?

更新:但是,当约束使用类似where T : IBase的接口时,将调用Base类中的方法(不是接口中的方法,这也是不可能的)。
这意味着系统实际上能够检测到远远超出类型约束的类型!那么为什么在类类型约束的情况下它不会超出类型约束?
这是否意味着实现接口的Base类中的方法具有方法的隐式override关键字?

测试代码:

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);
    }
}

输出:

0
0
0
0
1
1
1
2

4 个答案:

答案 0 :(得分:7)

除了使用dynamic个对象外,C#总是在编译时绑定方法 - 即使使用泛型。虚方法调用绑定到虚方法槽而不是实现方法,因此当它们在派生类对象上执行时,它们将被定向到派生类实现;尽管在运行时将确定时隙点的方法,但是在编译时对时隙的绑定发生。如果派生类方法声明为new而不是override,则使用派生类绑定的代码将使用derived-class方法,但使用基类绑定的代码将使用该基类 - 类方法。

要理解为什么会出现这种情况,想象一下是不是。如果课程Base声明方法int Foo(),而课程Derived:Base声明new string Foo(),会发生什么。如果具有约束T:Base的泛型类尝试在类型为Foo的对象上调用方法T,那么该方法的返回类型应该是什么?

答案 1 :(得分:6)

这是因为T被限制为具有Base的语义。我不能确切地告诉你在运行时类型绑定到底发生了什么,但这是我受过教育的猜测。

您没有正确覆盖该方法,而是通过“new”隐藏,如果您使用对基类的引用,则绕过任何隐藏。这就是躲藏的地方。

隐藏其他成员的成员只有在使用对其隐藏类型的引用时才会受到尊重。您始终可以通过使用对基类的引用来绕过隐藏的成员:

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

要正确覆盖方法并使此代码有效,请在基类中将方法标记为virtual,并在派生类中将其标记为override

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

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

您可以证明这一点,将您的通用约束更改为where T : Derived,并且应该点击“新”成员。

答案 2 :(得分:0)

这是由于运营商新的性质: 新的不像覆盖,创建一个与基本名称相同的函数,它掩盖基本方法但不覆盖它。

因此,没有适当的强制转换,如果引用的类型为Base,则将调用原始方法。

答案 3 :(得分:0)

new关键字只是隐藏方法而不是重载方法。您的非通用CallMethod似乎按预期工作的原因是因为方法签名需要Derived而不是Base

仿制药并不是真正的罪魁祸首。如果将方法签名更改为CallMethod(Base obj),您将看到与通用实现相同的“意外”行为,并获得以下输出:

0
0
0
0
0
0
0
1

如果您使Base.Method虚拟并使用Derived.Method覆盖它,请执行以下操作:

public class Base 
{
    public virtual void Method()
    {

    }
}

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

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

您将获得以下输出:

1
2
3
4
5
6
7
8

修改更新以匹配问题的更新输出。