意外的泛型行为

时间:2012-05-05 20:22:26

标签: c# generics

我正在和一个聊天的人讨论相关问题,我想出了这个代码,其行为与我预期的不同。

class Program
{
    static void Main(string[] args)
    {
        new Test<SomeObject>();
        Console.ReadKey();
    }
}

class SomeObject
{
    public SomeObject() { }

    public new string ToString()
    {
        return "Hello world.";
    }
}

class Test<T> where T : new()
{
    public Test()
    {
        T t = new T();
        object t1 = t;
        Console.WriteLine(t.ToString());
        Console.WriteLine(t1.ToString());
    }
}

输出结果为:

<ProjectName>.SomeObject
<ProjectName>.SomeObject

因为第一行是从泛型类型写的,我希望它使用SomeObject中定义的ToString()方法,因为这样的类型在运行时会变成什么呢?

3 个答案:

答案 0 :(得分:2)

我相信Ben Voigt在评论中给了你答案。

您可以通过将声明隐藏(new)方法实现的类型指定为通用约束来实现您期望的结果:

class Test<T> where T : SomeObject, new()
{
    public Test()
    {
        T t = new T();
        object t1 = t;
        Console.WriteLine(t.ToString());
        Console.WriteLine(t1.ToString());
    }
}

输出:

Hello world.
Program.SomeObject

编辑:编译器根据通用约束解析泛型类型的所有成员调用。这在Constraints on Type Parameters上的MSDN C#编程指南中暗示:

  

通过约束类型参数,可以将允许的操作和方法调用的数量增加到约束类型支持的操作和方法调用的数量,以及其继承层次结构中的所有类型。因此,当您设计泛型类或方法时,如果您将在简单赋值之外对泛型成员执行任何操作或调用System.Object不支持的任何方法,则必须对类型参数应用约束。

帮助澄清问题:想象一下,您在班上定义了一种新方法Foo

class SomeObject
{
    public SomeObject() { }

    public void Foo() { }
}

尝试调用Foo会导致编译时错误。编译器对泛型类型T唯一了解的是它有一个无参数构造函数 - 它不知道它可能定义的任何方法。

class Test<T> where T : new()
{
    public Test()
    {
        T t = new T();
        t.Foo();   // Error: 'T' does not contain a definition for 'Foo' 
                   //        and no extension method 'Foo' accepting a
                   //        first argument of type 'T' could be found
    }
}

但是,如果您将T约束为SomeObject类型,那么编译器会知道在Foo类中查找SomeObject的定义:

class Test<T> where T : SomeObject, new()
{
    public Test()
    {
        T t = new T();
        t.Foo();   // SomeObject.Foo gets called
    }
}

隐藏成员的推理非常相似。

答案 1 :(得分:1)

Test<T>中,编译器不知道T实际上是SomeObject,因为T没有约束。因此,它只能假设tobject,而对t.ToString()的调用会导致调用虚拟Object.ToString方法, {{ 1}}

答案 2 :(得分:0)

这与泛型完全无关,而且与您声明名为ToString方法而不是覆盖这一事实有关现有的。

如果你改写了这个方法,那么它就会被使用,这与早期绑定相比没有任何关系(如此处的其他答案所示)。

ToString引用上调用Tobject引用没有区别的原因是编译器无法验证 all {可以在这里使用的{1}}定义了那个新的T方法,因此它必须回退到 all 案件。

请注意,编译器将生成一个方法,该方法将由 ToString的所有变体使用,因此它必须使用它对T的知识,而你没有声明你从接口或类继承(虽然在这种情况下我怀疑它会产生影响)所以编译器无法知道在这种情况下T已被覆盖。

另一方面,如果声明有问题的ToStringT的后代,编译器会知道它有一个要使用的新 SomeObject方法,但仅凭ToString编译器无法使用此知识。