当两个重载具有相同的签名时,调用构造函数重载

时间:2009-08-18 11:04:27

标签: c# compiler-construction constructor overloading

考虑以下课程,

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是整数类型,两个构造函数都有相同的签名吗?为什么编译器不显示错误而不是选择执行重载?

4 个答案:

答案 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");