简体   繁体   English

歧义扩展方法调用

[英]Ambiguous extension method call

This code won't compile: 这段代码不会编译:

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"
    }
}

But if I delete last string arguments everything is fine: 但是,如果我删除最后一个string参数,一切都很好:

    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 {
    }

Question is - why these optional string arguments break compiler's choice of method call? 问题是-为什么这些可选的string参数破坏了编译器对方法调用的选择?

Added: Clarified question: I dont get why compiler can't choose right overload in the first case but could do it in second one? 补充:澄清的问题:我不明白为什么编译器在第一种情况下不能选择正确的重载,而在第二种情况下可以选择正确的重载?

Edited: [CallerMemberName] attribute is not a cause of a problem here so I've deleted it from question. 编辑: [CallerMemberName]属性不是这里引起问题的原因,因此我已将其从问题中删除。

@PetSerAl pointed to the spec in the comments already, but let me translate that to plain English: @PetSerAl已经在注释中指出了规范,但让我将其翻译为简单的英语:

The C# language has a rule that says an overload without omitted defaulted arguments is preferred over an overload with omitted defaulted arguments. C#语言有一条规则,规定不带默认参数的重载比带默认参数的重载优先。 This rule makes Foo(this A a, Exception e = null) a better match than Foo(this A a, T t, Exception e = null) . Foo(this A a, T t, Exception e = null)相比Foo(this A a, Exception e = null)此规则使Foo(this A a, Exception e = null)更好地匹配。

The C# language does not have a rule saying that an overload with one omitted defaulted argument is preferred over an overload with two omitted defaulted arguments. C#语言没有一条规则说,带有一个省略的默认参数的重载比带有两个省略的默认参数的重载更可取。 Because it does not have such a rule, Foo(this A a, Exception e = null, string s = "") is not preferred over Foo<T>(this A a, T t, Exception e = null, string s = "") . 因为它不具有这样的规则, Foo(this A a, Exception e = null, string s = "")优选的过Foo<T>(this A a, T t, Exception e = null, string s = "")

The easiest way to avoid this problem would normally be by providing additional overloads, instead of using default parameter values. 避免此问题的最简单方法通常是提供其他重载,而不是使用默认参数值。 You need default parameter values for CallerMemberName to work, but you can provide additional overloads that omit the Exception , and forward to the real implementation by passing null for that. 您需要使用默认参数值才能使CallerMemberName正常工作,但是您可以提供其他重载来忽略Exception ,并为此传递null来转发给实际实现。

Note: ensuring that Foo<T>(this A a, T t, string s = "") doesn't get picked when Foo(this A a, Exception e, string s = "") is available is going to be a tricky problem regardless. 注意:确保当Foo(this A a, Exception e, string s = "")可用时,不会选择Foo<T>(this A a, T t, string s = "")棘手的问题不管。 If your variable is statically typed as Exception , then the non-generic method will be preferred, but if it's statically typed as eg ArgumentException , then T=ArgumentException is a better match than the base class Exception , and the error in T=ArgumentException will be detected too late to pick the method you want to call. 如果您的变量被静态类型化为Exception ,那么将首选非泛型方法,但是如果它被静态类型化为例如ArgumentException ,则T=ArgumentException比基类Exception更好的匹配,并且T=ArgumentException的错误将被检测到来不及选择您要调用的方法。 Perhaps it would be safest to place T after Exception , and always require an exception being passed in (possibly null ) when the generic method is intended. 也许将T放在Exception 之后是最安全的,并且在打算使用泛型方法时始终要求将异常传入(可能为null )。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM