8

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

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

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

测试代码:

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

4 回答 4

7

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

要理解为什么会这样,想象一下如果不是这样。如果类Base声明了一个方法int Foo(),而一个类Derived:Base声明了一个new string Foo(). 如果具有约束的泛型类T:Base尝试调用Footype 对象的方法T,该方法的返回类型应该是什么?

于 2012-05-24T16:04:13.733 回答
6

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

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

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

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,它应该击中“新”成员。

于 2012-05-24T15:57:41.073 回答
0

这是由于运算符 new 的性质:New 与 override 不同,它创建一个与基本方法同名的函数,它掩盖基本方法但不覆盖它。

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

于 2012-05-24T15:58:15.787 回答
0

new关键字只是隐藏方法而不是重载它。您的非泛型CallMethod似乎按预期工作的原因是因为方法签名需要 aDerived而不是 a 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

编辑:更新以匹配问题的更新输出。

于 2012-05-24T16:09:15.463 回答