【问题标题】:Generic methods and method overloading泛型方法和方法重载
【发布时间】:2020-09-04 03:34:12
【问题描述】:

方法重载允许我们定义许多具有相同名称但具有不同参数集的方法(因此具有相同的名称但具有不同的签名)。

这两个方法重载了吗?

class A
{
    public static void MyMethod<T>(T myVal) { }
    public static void MyMethod(int myVal) { }
}

编辑:

语句A&lt;int&gt;.MyMethod(myInt);不应该抛出错误吗,因为构造类型A&lt;int&gt;有两个同名同签名的方法?

【问题讨论】:

  • 运行时总是会选择更具体的而不是通用的。

标签: c# generics


【解决方案1】:

这两个方法重载了吗?

是的。

不应该声明A&lt;int&gt;.MyMethod(myInt); 抛出错误,因为构造类型A&lt;int&gt; 有两个具有相同签名的方法?

这个问题没有意义; A 不是您声明的泛型类型。也许你想问:

语句A.MyMethod(myInt);是否应该导致编译器报告错误,因为有两个模棱两可的候选方法?

没有。正如其他人所说,在这种情况下,重载决议更喜欢非泛型版本。详情见下文。

或者你的意思是问:

首先声明类型 A 是否应该是非法的,因为在某种意义上它有两个具有相同签名的方法,MyMethod 和 MyMethod&lt;int&gt;?

没有。 A 型是完全合法的。 泛型是签名的一部分。因此,没有两个方法具有相同的签名,因为第一个具有通用参数零,第二个具有通用参数一。

或者你的意思是问:

class G<T> 
{
    public static void M(T t) {}
    public static void M(int t) {}
}

可以构造泛型类型G&lt;T&gt;,使其具有两个具有相同签名的方法。声明这样的类型合法吗?

是的,声明这样的类型是合法的。这通常是一个坏主意,但它是合法的。

然后你可能会反驳:

但我的 Addison-Wesley 发布的 C# 2.0 规范副本在第 479 页上声明“声明为相同名称的两个函数成员......必须具有参数类型,这样任何封闭构造类型都不能有两个具有相同姓名和签名的成员。“这是怎么回事?

最初设计 C# 2.0 时,这就是计划。然而,随后设计者意识到这种理想的模式将被视为非法:

class C<T> 
{
    public C(T t) { ... } // Create a C<T> from a given T
    public C(Stream s) { ... } // Deserialize a C<T> from disk
}

现在我们说对不起哥们,因为你可以说C&lt;Stream&gt;,导致两个构造函数统一,整个类是非法的。那将是不幸的。显然,任何人都不太可能用 Stream 作为类型参数来构造这个东西!

不幸的是,该规范在文本更新为最终版本之前就已出版。第 479 页的规则不是我们实施的。

继续代表您提出更多问题:

如果你调用G&lt;int&gt;.M(123),或者在原始示例中调用A.MyMethod(123),会发生什么?

当由于泛型构造而面临具有相同签名的两种方法时,当重载决议面临时,泛型构造的方法被认为比“自然”的方法“不太具体”。不太具体的方法会输给更具体的方法。

那么,如果重载解析有效,为什么这是一个坏主意?

A.MyMethod 的情况还不错;通常很容易明确地确定使用哪种方法。但是G&lt;int&gt;.M(123) 的情况要糟糕得多。 CLR 规则使这种情况成为“实现定义的行为”,因此任何旧的事情都可能发生。从技术上讲,CLR 可以拒绝验证构造类型G&lt;int&gt; 的程序。或者它可能会崩溃。事实上,它两者都没有。它在糟糕的情况下尽其所能。

有没有这种类型构造导致真正实现定义的行为的例子?

是的。有关详细信息,请参阅这些文章:

https://ericlippert.com/2006/04/05/odious-ambiguous-overloads-part-one/

https://ericlippert.com/2006/04/06/odious-ambiguous-overloads-part-two/

【讨论】:

  • @aspnetonc:正确。方法可以具有相同的名称,但每个方法都必须具有唯一的签名。 C# 语言规范将方法的签名定义为它的名称、通用参数和参数类型的组合(并注意,仅一个“out”和另一个“ref”不同的签名被认为是相同的签名.) CLR 对“签名”的定义略有不同,因为它还将参数类型和返回类型的返回类型和 modopt / modreq 修饰符视为签名的一部分。
  • @Joan:这使得重载解析变得极其困难。假设你有 int M(int x) 和 string M(string x)。您调用 M(N(q)) 并且您如何对 M 进行重载解析?嗯,首先你必须考虑参数 N(q) 的类型。如果解决该问题取决于返回类型,那么您将遇到先有鸡还是先有蛋的问题; N 的返回类型可以是字符串或 int,具体取决于选择的 M,这正是我们首先要确定的内容。
  • @Joan:现在,lambdas 确实有这个问题;如何对 lambda 的内部进行类型绑定取决于它的上下文,这可能取决于 lambda 的内部。让它在常见情况下有效地工作花了我一年的大部分时间,并且仍然存在病理情况,您必须进行指数级的大量绑定。这种情况仅限于深度嵌套的 lambda,这种情况很少见。嵌套函数调用很常见。一般来说,我们总是希望从表达式的内部到外部进行推理,而不是尝试双向。
  • @User437291:我不明白这个问题。听起来你在问“我不应该做一些不可能的事情吗?” - 你是应该还是不应该做一些不可能的事情是一个有争议的问题;你不会打算做一些不可能的事,不管它是否道德。假设答案是“当然,继续,编写一个不符合 CLR 方法签名唯一性规则的方法重载”。你甚至会如何开始这样做?您是否打算编写自己的元数据发射器,或者究竟是什么?
  • @user437291:因为 CLR 旨在实现 C# 以外的语言。许多语言的“签名冲突”规则不如 C# 的规则严格。例如,在 C++ 中,您可以有两个方法 void M(const int& x) 和 void M(int& x),它们是不同的方法,尽管它们的签名看起来非常相似。如果 CLR 不允许 C++-to-IL 编译器进行两个仅在 const-ness 上有所不同的重载,那对于编译器编写者来说将是一个负担,你不觉得吗?
【解决方案2】:

是的。 MyMethod(int myVal) 将在参数类型为 int 时调用,通用重载将为所有其他参数参数调用,即使参数参数可隐式转换为硬编码类型(或者是硬编码类型的派生类) .重载解析将最适合,而泛型重载将在编译时解析为完全匹配。

注意:您可以通过在方法调用中提供类型参数来显式调用泛型重载并使用int,正如 Steven Sudit 在他的回答中指出的那样。

short s = 1;
int i = s;
MyMethod(s); // Generic
MyMethod(i); // int
MyMethod((int)s); // int
MyMethod(1); // int
MyMethod<int>(1); // Generic**
MyMethod(1.0); // Generic
// etc.

【讨论】:

  • 我认为第二部分并不完全正确:看看我的答案。
  • @Steven,对泛型重载的显式调用完全是另一回事。我会记下的。
  • @aspnetonc,关于您的 MyMethod(int i) 与 MyMethod(T t) 以及它如何以明确的方式编译的问题,您不妨访问 Eric Lippert 的博客(谷歌搜索,不难找到)并使用联系链接。在那里提出您的问题或提供指向此处的链接。如果有人在此期间没有提供好的解释,他可能会插话。
  • 简短版本是 IL 显示对 MyMethod(int32) 的调用,如果你调用 MyMethod(1),MyMethod&lt;int32&gt;,如果你使用 MyMethod&lt;int&gt;(1),MyMethod&lt;float64&gt;,如果你使用 @ 987654329@。我不是 IL 专家,但看起来解析的类型成为方法名称的一部分,消除了歧义。不过,我可能完全不在基地。
【解决方案3】:

是的,他们是。他们将允许这样的代码:

A.MyMethod("a string"); // calls the generic version
A.MyMethod(42);  // calls the int version

【讨论】:

    【解决方案4】:

    是的,它们超载了。如果可用,编译器应该更喜欢显式方法签名而不是泛型方法。但是请注意,如果您可以避免这种过载,您可能应该这样做。有关于这种过载和意外行为的错误报告。

    https://connect.microsoft.com/VisualStudio/feedback/details/522202/c-3-0-generic-overload-call-resolution-from-within-generic-function

    【讨论】:

    • 实际上,该链接指向一个错误错误报告。记者只是不明白,显式方法在重载方面胜过泛型。因此,虽然您可能认为这是我们可能希望避免的复杂性,但我认为这不是由于任何实际错误。
    • 人们遇到的问题是你有约束的场景(不是签名的一部分!)或者你有一个Reptile和一个Lizard : Reptile,而Foo&lt;T&gt;(T t)被调用@ 987654325@ 而不是Foo(Reptile reptile)。原因?泛型将编译为与 Lizard 完全匹配,而不是与 Reptile 使用足够接近的匹配。
    • @Anthony:说得好。我想只有当我们首先将Lizard 向上转换为Reptile 时才能实现所需的行为,但它可能比这更微妙,因为当我们处理 int 类型之间的隐式转换时。
    【解决方案5】:

    是的。它们具有相同的名称“MyMethod”,但签名不同。然而,C# 规范专门处理这个问题,即当两者都是选项时,编译器会更喜欢非泛型版本而不是泛型版本。

    【讨论】:

      【解决方案6】:

      是的。在我的脑海中,如果你打电话给A.MyMethod(1);,它总是会运行第二种方法。您必须调用 A.MyMethod&lt;int&gt;(1); 来强制它运行第一个。

      【讨论】:

        猜你喜欢
        • 2013-06-16
        • 2013-03-09
        • 1970-01-01
        • 1970-01-01
        • 2015-06-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多