【问题标题】:Ambiguous extension method call模糊扩展方法调用
【发布时间】:2017-01-22 19:38:35
【问题描述】:

此代码无法编译:

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] 属性在这里不是问题的原因,所以我已将其从问题中删除。

【问题讨论】:

  • 只是为了澄清:单独删除[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 tException t

标签: c#


【解决方案1】:

@PetSerAl 已经指出了 cmets 中的规范,但让我将其翻译成简单的英语:

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&lt;T&gt;(this A a, T t, Exception e = null, string s = "")

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

注意:确保Foo&lt;T&gt;(this A a, T t, string s = "")Foo(this A a, Exception e, string s = "") 可用时不会被选中,无论如何这将是一个棘手的问题。如果您的变量静态类型为Exception,那么非泛型方法将是首选,但如果它是静态类型的,例如ArgumentException,那么T=ArgumentException 是比基类Exception 更好的匹配,T=ArgumentException 中的错误将被检测到来不及选择你要调用的方法。或许将T 放在Exception 之后 是最安全的,并且在使用泛型方法时总是需要传入一个异常(可能是null)。

【讨论】:

  • 很好的答案。是不是意味着where T : class, IB这样的约束在重载解析时会被编译器完全忽略?
  • @Szer 是,[通用约束不是方法签名的一部分。](@Szer 是,通用 constraints are not part of a methods signature.
  • 这是 C# 语言规范的准确引用:• 否则,如果 MP 的所有参数都有相应的参数,而默认参数需要替换为 MQ 中的至少一个可选参数,则 MP比MQ好。
  • @Szer InBetween 是对的,即使您避免使用默认参数值,这也会导致我错过的问题。现在进行了编辑以尝试解决这个问题。
  • 我想确实存在的 C# 语言规则确保我们始终可以通过足够的努力来明确地转换和给出所有参数,即使它们具有默认值(或类型参数的推断值),调用任何“直接”特定的重载。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-04-03
  • 1970-01-01
  • 2014-06-22
  • 2020-03-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多