我正在和一个聊天的人讨论相关问题,我想出了这个代码,其行为与我预期的不同。
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()方法,因为这样的类型在运行时会变成什么呢?
答案 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
没有约束。因此,它只能假设t
是object
,而对t.ToString()
的调用会导致调用虚拟Object.ToString
方法,不 {{ 1}}
答案 2 :(得分:0)
这与泛型完全无关,而且与您声明名为ToString
的新方法而不是覆盖这一事实有关现有的。
如果你改写了这个方法,那么它就会被使用,这与早期绑定相比没有任何关系(如此处的其他答案所示)。
在ToString
引用上调用T
与object
引用没有区别的原因是编译器无法验证 all {可以在这里使用的{1}}定义了那个新的T
方法,因此它必须回退到 all 案件。
请注意,编译器将生成一个方法,该方法将由 ToString
的所有变体使用,因此它必须使用它对T的知识,而你没有声明你从接口或类继承(虽然在这种情况下我怀疑它会产生影响)所以编译器无法知道在这种情况下T
已被覆盖。
另一方面,如果已声明有问题的ToString
是T
的后代,编译器会知道它有一个要使用的新 SomeObject
方法,但仅凭ToString
编译器无法使用此知识。