考虑下面的课程,
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
没有歧义,因为编译器将选择匹配的最具体的
Foo(...)
重载。由于具有泛型类型参数的方法被认为比相应的非泛型方法不太具体,因此当 Foo(T)
时,Foo(int)
比 T == int
更不具体。因此,您正在调用 Foo(int)
重载。
您的第一种情况(有两个
Foo(int)
定义)是一个错误,因为编译器只允许一个具有完全相同签名的方法的定义,而您有两个。
在设计 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-ambigously-overloads-part-two.aspx
Eric Lippert 最近在博客中谈到了这一点。
事实是,它们并不具有相同的签名 - 一个使用泛型,而另一个则没有。
有了这些方法,您还可以使用非 int 对象来调用它:
Foo<string> foo = new Foo<string>("Hello World");