考虑以下课程,
class Foo
{
public Foo(int count)
{
/* .. */
}
public Foo(int count)
{
/* .. */
}
}
以上代码无效且无法编译。现在考虑以下代码,
class Foo<T>
{
public Foo(int count)
{
/* .. */
}
public Foo(T t)
{
/* .. */
}
}
static void Main(string[] args)
{
Foo<int> foo = new Foo<int>(1);
}
以上代码有效且编译良好。它调用 Foo(int count)。
我的问题是,如果第一个无效,第二个如何有效?我知道类 Foo&lt; T&gt; 是有效的,因为T和int是不同的类型。但是当它像 Foo&lt; int&gt;一样被使用时foo = new Foo&lt; int&gt;(1),T是整数类型,两个构造函数都有相同的签名吗?为什么编译器不显示错误而不是选择执行重载?
答案 0 :(得分:23)
没有歧义,因为编译器会选择匹配的Foo(...)
的最具体的重载。由于具有泛型类型参数的方法被认为不如相应的非泛型方法具体,因此Foo(T)
在Foo(int)
时不具有T == int
的特异性。因此,您正在调用Foo(int)
重载。
你的第一个案例(有两个Foo(int)
定义)是一个错误,因为编译器只允许一个具有完全相同签名的方法的定义,并且你有两个。
答案 1 :(得分:19)
当设计C#2.0和CLR中的通用类型系统时,您的问题引起了激烈的争论。事实上,A-W发布的“绑定”C#2.0规范实际上有一个错误的规则!有四种可能性:
1)声明在某些构造下可能存在模糊的泛型类是非法的。 (这是绑定规范错误地说的规则。)所以你的Foo<T>
声明是非法的。
2)以造成歧义的方式构造泛型类是非法的。声明Foo<T>
是合法的,构建Foo<double>
将是合法的,但构建Foo<int>
将是非法的。
3)使一切合法并使用重载决议技巧来确定通用或非通用版本是否更好。 (这就是C#实际上做的。)
4)做一些我没有想过的事情。
规则#1是一个坏主意,因为它使一些非常常见且无害的场景变得不可能。考虑例如:
class C<T>
{
public C(T t) { ... } // construct a C that wraps a T
public C(Stream state) { ... } // construct a C based on some serialized state from disk
}
您希望这只是非法因为C<Stream>
含糊不清?呸。规则#1是一个坏主意,所以我们废弃它。
不幸的是,它并不那么简单。 IIRC CLI规则规定允许实现拒绝实际导致签名含糊不清的非法构造。也就是说,CLI规则类似于规则#2,而C#实际上实现了规则#3。这意味着理论上可以将合法的C#程序转化为非法代码,这是非常不幸的。
关于这些歧义如何让我们的生活变得悲惨的更多想法,这里有几篇关于这个主题的文章:
http://blogs.msdn.com/ericlippert/archive/2006/04/05/569085.aspx
http://blogs.msdn.com/ericlippert/archive/2006/04/06/odious-ambiguous-overloads-part-two.aspx
答案 2 :(得分:9)
Eric Lippert blogged 最近有关此事。
答案 3 :(得分:0)
事实是,他们不是都有相同的签名 - 一个是使用泛型而另一个不是。
使用这些方法,您也可以使用非int对象调用它:
Foo<string> foo = new Foo<string>("Hello World");