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

Posted

技术标签:

【中文标题】当两个重载具有相同的签名时调用构造函数重载【英文标题】:Calling constructor overload when both overload have same signature 【发布时间】:2010-11-20 13:28:53 【问题描述】:

考虑以下类,

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 是有效的,因为 T 和 int 是不同的类型。但是当它像 Foo foo = new Foo(1) 那样使用时,T 正在获取整数类型并且两个构造函数将具有相同的签名,对吗?为什么编译器不显示错误而不是选择一个重载来执行?

【问题讨论】:

【参考方案1】:

没有歧义,因为编译器会选择匹配的最具体的Foo(...) 重载。由于具有泛型类型参数的方法被认为不如相应的非泛型方法具体,因此T == intFoo(T) 的具体性低于Foo(int)。因此,您正在调用 Foo(int) 重载。

您的第一种情况(有两个Foo(int) 定义)是一个错误,因为编译器将只允许定义一个具有完全相同签名的方法,而您有两个。

【讨论】:

我检查了语言规范,但它并没有说泛型参数不如具体类型那么具体。然而,非泛型方法被认为比泛型方法“更好”。 你是对的,这不是很好的措辞。我的意思是说“具有泛型类型参数的方法......”。 顺便说一句,对于好奇的人,我认为 Thorarin 所说的部分是 §14.4.2.2,它定义了“更好的函数成员”。 我明白这一点。但是 IMO,编译器应该发出警告,因为这种行为会造成混淆。 不确定编译器如何通过警告的方式在这里做任何有用的事情——一旦你定义了 Foo(T t)——所有其他具有该名称的单参数方法都会违反那种检查你的建议。【参考方案2】:

在设计 C# 2.0 和 CLR 中的泛型类型系统时,您的问题引起了激烈的争论。如此火爆,事实上,A-W 发布的“绑定”C# 2.0 规范中居然有错误的规则!有四种可能:

1) 声明在某些构造下可能不明确的泛型类是非法的。 (这就是绑定规范错误地说的规则。)所以你的Foo&lt;T&gt; 声明是非法的。

2) 以产生歧义的方式构造泛型类是非法的。声明Foo&lt;T&gt; 是合法的,构造Foo&lt;double&gt; 是合法的,但构造Foo&lt;int&gt; 是非法的。

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&lt;Stream&gt; 模棱两可,你就认为它是非法的吗?呸。规则 #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

【讨论】:

猜第二个 Foo 必须是 int “翻译成非法代码的合法 C# 程序”这怎么可能呢?我认为翻译结果是newobj 指令和方法标记,其中Foo&lt;T=int&gt;.ctor(int)Foo&lt;T=int&gt;.ctor(T) 具有不同的方法标记。因此,IL 中没有歧义。我错过了什么吗? @BenVoigt:首先,如果我说“无法验证的代码”或“具有实现定义行为的代码”,而不是“非法代码”,它会更准确。产生签名冲突的通用构造的后果是微妙的。例如,假设我们有两个具有相同签名的方法,并希望在元数据中指出其中一个是特定接口方法的实现。方法实现表中没有元数据结构可以消除具有相同签名的两个方法之间的歧义。还有其他类似的奇怪案例。 @BenVoigt:您可能是对的,在构造函数的特定情况下,不会有问题。【参考方案3】:

Eric Lippert blogged 最近关于这个。

【讨论】:

而且,显然,他在这里也回答了这个问题。【参考方案4】:

事实是它们没有相同的签名 - 一个使用泛型而另一个没有。

有了这些方法,您还可以使用非 int 对象调用它:

Foo<string> foo = new Foo<string>("Hello World");

【讨论】:

以上是关于当两个重载具有相同的签名时调用构造函数重载的主要内容,如果未能解决你的问题,请参考以下文章

constexpr 重载

C++类-构造函数的重载

C++类-构造函数的重载

为啥 TypeScript 中没有重载的构造函数实现?

重写和重载的三点区别

构造函数的定义和构造函数的重载