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