【问题标题】:Why does the compiler give an ambiguous invocation error when passing inherited types?为什么编译器在传递继承类型时会给出模棱两可的调用错误?
【发布时间】:2013-11-04 00:52:10
【问题描述】:

C# 编译器中发生了什么导致以下不明确的调用编译错误?

同样的问题适用于扩展方法,或者当TestClass 是通用的并且使用实例而不是静态方法时。

我意识到它很容易解决(例如,在方法调用上将 secondInstance 转换为 Test1),但我更好奇编译器为方法选择应用了什么逻辑。

我的假设是编译器在方法检测上应用了某种程度的特异性度量(如 CSS)以确定最具体的匹配 - 这是无效的吗?

class Type1 { }
class Type2 : Type1 {}

class TestClass
{
    public static void Do<T>(T something, object o) where T : Type1
    {} 

    public static void Do(Type1 something, string o)
    {}
}

void Main()
{
    var firstInstance = new Type1();
    TestClass.Do(firstInstance, new object()); // Calls Do<T>(T, obj)
    TestClass.Do(firstInstance, "Test"); // Calls Do(Type1, string)

    var secondInstance = new Type2();
    TestClass.Do(secondInstance, new object()); // Calls Do<T>(T, obj)
    TestClass.Do(secondInstance, "Test"); // "The call is ambiguous" compile error
}

// 编辑:mike z 提出了一个概念,我将其解释为“投射距离”被用作方法选择的权重。对此的测试似乎支持这一点(尽管我不确定 Type->Generic Type 是如何加权的)。

// Add the following two methods to TestClass
public static void Do<T>(T something) where T : Type1
{} 

public static void Do(Type1 something)
{}

public static void Do<T>(T something, object o) where T : Type1
{} 

public static void Do(Type1 something, string o)
{}

void Main()
{
    var firstInstance = new Type1();

    // Can't select string
    TestClass.Do(firstInstance, new object()); // Calls Do<T>(T, obj)

    // Do() distance is 0, Do<T> distance is 1
    TestClass.Do(firstInstance, "Test"); // Calls Do(Type1, string)

    // Do() distance is 0, Do<T> distance is ? (but more than 0?)
    TestClass.Do(firstInstance); // Calls Do(Type1)

    var secondInstance = new Type2();

    // Can't select string
    TestClass.Do(secondInstance, new object()); // Calls Do<T>(T, obj)

    // Do() distance is 1, Do<T> distance is 1
    TestClass.Do(secondInstance, "Test"); // "The call is ambiguous" compile error

    // Do() distance is 1, Do<T> distance is ? (but less than 1?)
    TestClass.Do(secondInstance); // Calls Do<T>(T)

}

【问题讨论】:

  • 它不知道选择哪个'Do',因为字符串也是一个对象。您可以切换参数顺序,或者更明确地调用 'Do'
  • 当第一个参数明确为Type1时,它设法确定stringobject更具体,但当第一个参数继承自Type1时则不然
  • 我真的会尽量避免这种情况。如果您不将泛型方法命名为与非泛型方法相同,则可以避免很多混淆。顺便说一句,课程也是如此。

标签: c#


【解决方案1】:

第 7.5.3 节介绍了重载解决方案。这很复杂,但基本思想是编译器将根据它需要进行的转换的数量和类型来确定“最佳”重载。

对于第 1 种情况,泛型重载存在完全匹配的类型。
对于案例 2,非泛型重载存在精确类型匹配。
对于案例 3,泛型重载是完全匹配的。注意:您的评论不正确。 T 的类型将是 Type2
对于案例 4,泛型重载需要从字符串转换为对象,而非泛型方法需要从 Type2 转换为 Type1。请注意,这些都是对基本类型的引用转换。由于在这两种情况下都需要一种相同类型的转换,因此编译器拒绝为您做出决定并给您一个错误,即调用不明确。没有“最佳”匹配。

【讨论】:

  • 更新了不正确的 cmets,谢谢。我想你是对的。我不确定通用的“铸造/选择”是如何加权的。我添加了一个更新的样本来测试你的理论。
  • @MattMitchell 我不太确定我会称之为“距离”,但它就是这样。请注意,具有完全匹配参数类型的重载始终被认为比具有泛型参数的其他等效重载更好,正如您后面的一些示例所示。
猜你喜欢
  • 1970-01-01
  • 2012-09-29
  • 1970-01-01
  • 2011-06-19
  • 1970-01-01
  • 2018-03-16
  • 2011-07-07
  • 1970-01-01
  • 2011-04-20
相关资源
最近更新 更多