【问题标题】:C# compiler chooses wrong extension methodC# 编译器选择了错误的扩展方法
【发布时间】:2023-03-29 02:00:01
【问题描述】:

考虑这段代码:

using System.Linq;

namespace ExtensionMethodIssue
{
    static class Program
    {
        static void Main(string[] args)
        {
            var a = new[] { 1 };
            var b = new[] { 1, 2 }.Where(a.Contains).ToList();
            var c = new[] { 1, 2 }.Where(i => a.Contains(i)).ToList();
        }
    }
}

代码编译成功。 然后我添加了nuget包“itext7 7.0.4”,现在编译失败是因为:

//Error CS0122: 'KernelExtensions.Contains<TKey, TValue>(IDictionary<TKey, TValue>, TKey)' is inaccessible due to its protection level
var b = new[] { 1, 2, 3 }.Where(a.Contains).ToList();

// This is still ok.
var c = new[] { 1, 2, 3 }.Where(i => a.Contains(i)).ToList();

原因是 itext7 库在 global 命名空间 (here it is) 中有一个带有扩展方法的 internal 类。

internal static class KernelExtensions {
    public static bool Contains<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key) {
        return dictionary.ContainsKey(key);
}
}

由于某种原因,编译器会从全局命名空间中选择具有不兼容签名的不可访问扩展方法,而不是从 LINQ 命名空间中选择具有兼容签名的可访问扩展方法。

问题是:根据语言规范,这种行为是预期的还是编译器中的错误?为什么它只在“方法组”的情况下失败并且仍然与i =&gt; a.Contains(i)一起工作?

【问题讨论】:

  • 方法组,不断给予的礼物。这显然是一个又大又肥的臭虫——不能通过在项目本身中添加一个带有扩展方法签名的类来重现,所以它是双重痛苦的(因为另一个程序集的内部应该是最少的您的顾虑)。
  • 哦,实际上,它变得更好了——如果你声明自己的static class Foo { public static bool Contains&lt;TKey, TValue&gt;(this IDictionary&lt;TKey, TValue&gt; dictionary, TKey key) =&gt; throw new NotImplementedException(); };,问题就消失了(不,这个方法没有被调用,它只是修复了重载决议)。此类的可见性或命名空间似乎并不重要。
  • 啊,不,附录:如果该方法是可访问的(publicinternal),那一切都好。但是,如果是private,编译器会抱怨Foo.Contains 不可访问。这可能会为事情如何跨程序集工作提供线索,因为另一个程序集的internal 方法同样无法访问——我怀疑这里存在愚蠢的一致性。我认为,Contains 被考虑的事实并不是一个错误,因为方法组是如何工作的(最初不考虑类型)。甚至static bool Contains(this bool x) 也会被考虑。
  • 显式类型参数掩盖了这个问题,因为这两个版本有不同数量的类型参数:a.Contains&lt;int&gt;
  • 我在 GitHub 上的C# compiler chooses inaccessible extension method over accessible one Roslyn 问题中描述了您的案例。

标签: c# .net


【解决方案1】:

这是一个编译器错误,一个不可访问的函数不应该影响重载决议。

一般的解决方法是使用 lambdas 作为参数而不是方法组,因为重载解析似乎可以很好地处理它们。在您的特定场景中哪个更快/更高效并不明显,请根据需要使用相关性能指标进行微优化。

在这种特殊情况下,您还可以使用其他扩展方法,例如 Enumerable.Intersect(),如果您正在使用集合,并且不关心重复,Enumerable.Join() 或简单循环。

更多信息请查看:

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多