为何C# 7.2中Foo的两次调用一次编译成功一次出现歧义?
为什么传递方法组会触发重载歧义,而Lambda却不会?
这是个非常有意思的C#重载解析细节,咱们一步步拆解来看:
先看Lambda调用Foo(() => Bar())的情况
当你传递lambda表达式() => Bar()时,编译器的处理流程是这样的:
- 遍历
Foo的两个重载,逐个检查lambda是否能隐式转换为对应的委托类型:- 对于
Foo(Func<int>):lambda的返回值是int(和Bar()的返回类型一致),完美匹配Func<int>的签名,转换可行。 - 对于
Foo(Func<string>):lambda返回int,而int无法隐式转换为string,所以这个转换完全不可行,对应的Foo重载被直接排除。
- 对于
- 最后只剩下一个可行的重载,自然不会有歧义,编译顺利通过。
再看方法组调用Foo(Bar)的情况
当你直接传递方法组Bar时,编译器的重载解析逻辑发生了变化:
- 首先,编译器会对每个
Foo重载做一个初步的适用性检查——只看方法组的参数列表是否和委托类型匹配:Bar是无参方法,Func<int>和Func<string>也都是无参委托,所以两个重载都通过了这个初步检查,被保留为候选。 - 接下来,编译器需要从这两个候选中选出“更好”的那个,但这里出现了问题:
虽然Bar到Func<int>是精确匹配(返回类型一致),到Func<string>是返回类型不兼容,但C#的重载解析规则中,返回类型的匹配程度不会被用来优先选择重载——规则只关注参数的转换质量,而这里两个候选的参数都是方法组到委托的转换,编译器无法判定哪个转换“更优”。 - 结果就是编译器认为两个重载都有资格被调用,从而抛出“调用歧义”的错误。
为什么移除Func<int>重载后会报错?
当只剩下Foo(Func<string>)时,编译器会做完整的转换检查,这时候才会发现Bar的返回类型int无法隐式转换为string,所以明确提示签名不匹配。这说明编译器并非没有判断能力,而是在多重载的场景下,初步检查的逻辑优先于完整的转换验证,导致了歧义。
内容的提问来源于stack exchange,提问作者eoinmullan
相关产品推荐
相关产品推荐

