C# 不明确的扩展方法调用
此代码不会编译:C# 不明确的扩展方法调用,c#,C#,此代码不会编译: using System; using System.Runtime.CompilerServices; static class Extensions { public static void Foo(this A a, Exception e = null, string memberName = "") { } public static void Foo<T>(this A a, T t, Exception e = null,
using System;
using System.Runtime.CompilerServices;
static class Extensions {
public static void Foo(this A a, Exception e = null, string memberName = "") {
}
public static void Foo<T>(this A a, T t, Exception e = null, string memberName = "")
where T : class, IB {
}
}
interface IB { }
class A { }
class Program {
public static void Main() {
var a = new A();
var e = new Exception();
a.Foo(e); //<- Compile error "ambiguous call"
}
}
问题是-为什么这些可选的string
参数会破坏编译器对方法调用的选择
添加:
澄清问题:我不明白为什么编译器不能在第一种情况下选择正确的重载,但在第二种情况下可以选择正确的重载
编辑:
[CallerMemberName]
属性不是这里出现问题的原因,所以我从问题中删除了它。@PetSerAl已经指出了注释中的规范,但让我将其翻译成简单的英语:
C#语言有一条规则,即不省略默认参数的重载优于省略默认参数的重载。此规则使Foo(此A,异常e=null)
比Foo(此A,T,异常e=null)
更匹配
C#语言没有一条规则规定,一个省略了默认参数的重载比两个省略了默认参数的重载更可取。因为它没有这样的规则,Foo(这是a,异常e=null,字符串s=“”)
并不比Foo(这是a,T,异常e=null,字符串s=“”)更受欢迎。
避免此问题的最简单方法通常是提供额外的重载,而不是使用默认参数值。您需要CallerMemberName
的默认参数值才能工作,但您可以提供省略异常的额外重载,并通过传递null
将其转发到实际实现
注意:确保Foo(此A,T,字符串s=“”)
在Foo(此A,异常e,字符串s=“”)
可用时不会被选中,这将是一个棘手的问题。如果变量静态类型为Exception
,则首选非泛型方法,但如果变量静态类型为ArgumentException
,则T=ArgumentException
比基类Exception
更匹配,而T=ArgumentException
中的错误将被检测得太晚,无法选择要调用的方法。也许最安全的做法是将T
放在Exception
之后,并在使用泛型方法时始终要求传入异常(可能是null
)。只是澄清一下:仅删除[CallerMemberName]
属性没有任何效果,编译器输出错误。删除整个参数[CallerMemberName]string memberName=“
会产生效果,代码将编译。这是我观察到的。对吗?C#规范:7.5.3.2更好的函数成员:否则,如果MP的所有参数都有相应的参数,而默认参数需要替换MQ中的至少一个可选参数,则MP比MQ更好。该规则适用于第二种情况,但不能应用于第一种情况。如果仅删除属性[CallerMemberName]
,但在两种方法中都保留参数string memberName=“”
,那么会发生什么情况?编辑:@OndrejTucny已经说过了,我在发帖后看到了。这仍然是一个好问题。@JeppeStigNielsen在LINQPad 4.57(C#5)中尝试了这一点,但它不会编译。@Szer这确实是一个模棱两可的调用,因为e和memberName的可选参数编译器假设t
是异常t
很好的答案。这是否意味着当涉及到重载解析时,编译器会完全忽略诸如其中T:class,IB
之类的约束?@Szer-yes[泛型约束不是方法签名的一部分。](@Szer yes,generic这里有一个来自C#Language规范的精确引用:•否则,如果MP的所有参数都有一个对应的参数,而MQ中至少有一个可选参数需要替换为默认参数,那么MP比MQ好。@Szer-InBetween是正确的,这可能会导致我错过的问题,即使您避免t他给出了默认参数值。现在也对其进行了编辑,试图解决这一问题。我想,确实存在的C语言规则确保我们能够始终通过足够的努力来强制转换并显式地给出所有参数,即使它们有默认值(或类型参数的推断值),直接调用任何特定重载。
public static void Foo(this A a, Exception e = null) {
}
public static void Foo<T>(this A a, T t, Exception e = null)
where T : class, IB {
}