不明确的扩展方法调用

时间:2017-01-22 10:45:40

标签: 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, 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个参数,一切都很好:

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

问题是 - 为什么这些可选的string参数会破坏编译器对方法调用的选择?

加了: 澄清的问题:我不明白为什么编译器在第一种情况下不能选择正确的重载但是可以在第二种情况下进行?

编辑: [CallerMemberName]属性不是导致问题的原因,因此我已将其删除。

1 个答案:

答案 0 :(得分:6)

@PetSerAl已经在评论中指出了规范,但让我把它翻译成普通英语:

C#语言有一条规则,说明没有省略默认参数的重载优先于省略默认参数的重载。此规则使Foo(this A a, Exception e = null)Foo(this A a, T t, Exception e = null)匹配得更好。

C#语言没有规则说一个带有一个省略的默认参数的重载优先于带有两个省略的默认参数的重载。由于它没有这样的规则,Foo(this A a, Exception e = null, string s = "") 优先于Foo<T>(this A a, T t, Exception e = null, string s = "")

避免此问题的最简单方法通常是提供额外的重载,而不是使用默认参数值。您需要CallerMemberName的默认参数值才能生效,但是您可以提供省略Exception的其他重载,并通过传递null转发到实际实现。

注意:确保在Foo<T>(this A a, T t, string s = "")可用时不会选择Foo(this A a, Exception e, string s = ""),这无论如何都将是一个棘手的问题。如果您的变量被静态输入为Exception,则非首选方法将是首选方法,但如果将其静态输入为例如ArgumentExceptionT=ArgumentException,然后Exception是一个比基类T=ArgumentException更好的匹配,T中的错误将被检测到太晚,无法选择您想要调用的方法。在{/ em> Exception之后放置null 可能是最安全的,并且在打算使用泛型方法时,总是需要传入异常(可能是[chrome.identity][1])。 / p>