当派生类型为1作为参数传递时,双向隐式可转换类型的重载之间的模糊调用

(试图找到一个总结问题的标题可能是一项非常艰巨的任务!)

我有以下类与一些重载方法,产生一个调用歧义编译器错误:

public class MyClass { public static void OverloadedMethod(MyClass l) { } public static void OverloadedMethod(MyCastableClass l) { } //Try commenting this out separately from the next implicit operator. //Comment out the resulting offending casts in Test() as well. public static implicit operator MyCastableClass(MyClass l) { return new MyCastableClass(); } //Try commenting this out separately from the previous implicit operator. //Comment out the resulting offending casts in Test() as well. public static implicit operator MyClass(MyCastableClass l) { return new MyClass(); } static void Test() { MyDerivedClass derived = new MyDerivedClass(); MyClass class1 = new MyClass(); MyClass class2 = new MyDerivedClass(); MyClass class3 = new MyCastableClass(); MyCastableClass castableClass1 = new MyCastableClass(); MyCastableClass castableClass2 = new MyClass(); MyCastableClass castableClass3 = new MyDerivedClass(); OverloadedMethod(derived); //Ambiguous call between OverloadedMethod(MyClass l) and OverloadedMethod(MyCastableClass l) OverloadedMethod(class1); OverloadedMethod(class2); OverloadedMethod(class3); OverloadedMethod(castableClass1); OverloadedMethod(castableClass2); OverloadedMethod(castableClass3); } public class MyDerivedClass : MyClass { } public class MyCastableClass { } 

有两个非常有趣的事情需要注意:

  1. 注释掉任何隐式运算符方法可消除歧义。
  2. 尝试重命名VS中的第一个方法重载将重命名Test()方法中的前四个调用!

这自然会带来两个问题:

  1. 什么是编译器错误背后的逻辑(即编译器如何达到歧义)?
  2. 这个设计有什么问题吗? 直观地说,应该没有歧义,并且应该在第一个方法重载( OverloadedMethod(MyClass l, MyClass r) )中解决违规调用OverloadedMethod(MyClass l, MyClass r)因为MyDerivedClassMyClass更密切相关,而不是可转换但不相关的MyCastableClass 。 此外,VS重构似乎也同意这种直觉。

编辑:在玩了VS重构之后,我看到VS将有问题的方法调用与代码中定义的第一个重载相匹配。 因此,如果我们交换两个重载VS,则将违规调用与具有MyCastableClass参数的调用匹配。 这些问题仍然有效。

什么是编译器错误背后的逻辑(即编译器如何达到歧义)?

首先,我们必须确定方法组中的方法 。 显然,方法组中有两种方法。

其次,我们必须确定这两种方法中哪一种适用 。 也就是说,每个参数都可以隐式转换为相应的参数类型。 显然,这两种方法都适用。

第三,鉴于存在多种适用方法,必须确定唯一的最佳方法。 在只有两个方法,每个只有一个参数的情况下,规则是从参数参数类型转换必须比另一个更好

关于什么使一次转换比另一种转换更好的规则在规范的7.5.3.5节中,为方便起见,我在这里引用:

给定从类型S转换为类型T1的转换C1,以及从类型S转换为类型T2的转换C2,如果存在以下至少一个,则C1是比C2更好的转换:

•存在从S到T1的身份转换,但不存在从S到T2的身份转换

•T1是比T2更好的转换目标

给定两种不同类型T1和T2,如果至少有以下一种情况,则T1是比T2更好的转换目标:

•存在从T1到T2的隐式转换,并且不存在从T2到T1的隐式转换

此规则的目的是确定哪种类型更具体 。 如果每个香蕉都是水果但不是每个水果都是香蕉,那么香蕉比水果更具体。

•T1是有符号整数类型,T2是无符号整数类型。

在列表中运行。 是否存在从MyDerivedClassMyCastableClassMyClass身份转换? 不。是否存在从MyClassMyCastableClass的隐式转换,而不是另一种隐式转换? 没有。没有理由认为这两种类型都比另一种更具体。 是整体类型? 没有。

因此,没有任何依据可以确定一个人比另一个人更好,因此这是不明确的。

这个设计有什么问题吗?

问题回答了自己。 你发现了其中一个问题。

直观地说,应该没有歧义,并且应该在第一个方法重载中解决违规调用,因为MyDerivedClass与MyClass关系更密切

虽然这对您来说可能很直观,但在这种情况下 ,规范用户定义的转换和任何其他隐式转换之间并没有区别。 但是我注意到,在一些罕见的情况下,你的干扰确实很重要 ; 有关详细信息,请参阅我关于链式用户定义转换的文章。 ( http://blogs.msdn.com/b/ericlippert/archive/2007/04/16/chained-user-defined-explicit-conversions-in-c.aspx

编译错误背后的逻辑是什么?

好吧,编译器根据一些事情确定签名。 参数的数量和类型位于名称旁边,这是最重要的名称之一。 编译器检查方法调用是否不明确。 它不仅使用参数的实际类型,还使用它可以式转换的类型(请注意,显式转换不在图片中,这里不使用它们)。

这给出了您描述的问题。

这个设计有什么问题吗?

是。 模棱两可的方法是许多问题的根源。 特别是在使用变量类型时,比如dynamic 。 即使在这种情况下,编译器也无法选择调用哪种方法,这很糟糕。 我们希望软件具有确定性,并且使用此代码,它不可能。

你没有要求它,但我想最好的选择是:

  1. 重新考虑你的设计。 你真的需要implicit演员吗? 如果是这样,为什么你需要两种方法而不是一种?
  2. 使用explicit转换而不是implicit转换,使转换成为编译器可以理解的有意选择。