考虑以下两种扩展方法:
using System;
using System.Collections.Generic;
using System.Linq;
public static class Extensions
{
public static bool Contains(this IEnumerable self, object obj)
{
foreach (object o in self)
{
if (Object.Equals(o, obj))
{
return true;
}
}
return false;
}
public static bool ContainsEither<T>(this IEnumerable<T> self, T arg1, T arg2)
{
return self.Contains(arg1) || self.Contains(arg2);
}
}
当我编写第二个方法时,我打算调用泛型LINQ Enumerable.Contains<T>
方法(从用法推断出类型参数)。但是,我发现它实际上是调用第一种方法(我的自定义Contains()
扩展方法。当我注释掉我的Contains()
方法时,第二种方法使用Enumerable.Contains<T>()
方法编译得很好
我的问题是,为什么编译器选择带有Contains()
参数的非IEnumerable
参数的Enumerable.Contains<T>()
方法和IEnumerable<T>
参数?我希望它选择Enumerable.Contains<T>()
,因为IEnumerable<T>
的派生程度高于IEnumerable
。
更新:感谢Jon Skeet的出色回答。知道设计师选择这种方式的原因会很有趣。我花了一些时间探索这个问题的重要案例,我想到了一个(授予它有点伸手可及的距离)
public static double Average(this IEnumerable self)
{
double sum = 0;
long count = 0;
foreach (object obj in self)
{
sum += Convert.ToDouble(obj);
nItems++;
}
return sum / count;
}
object[] junk = new object[] { "9000", (byte)99, -8.555, 3154254325345UL };
double[] clean = new double[] { 9000, 99, -8.555, 3154254325345 };
double junkAvg = junk.Average();
double cleanAvg = clean.Average(); // compiler is choosing your Average() method where you'd prefer it to choose Enumerable.Average(IEnumerable<double>)
答案 0 :(得分:20)
我的问题是,为什么编译器选择带有
IEnumerable
参数的非泛型Enumerable.Contains<T>()
参数的Contains()方法并带有IEnumerable<T>
参数?
因为它位于包含调用它的方法的同一名称空间中。在该命名空间内声明的类型实际上优先于在导入的命名空间中声明的类型。
从C#5规范,第7.6.5.2节:
对C的搜索过程如下:
- 从最近的封闭命名空间声明开始,继续每个封闭的命名空间声明,并以包含的编译单元结束,连续尝试查找一组候选扩展方法:
- 如果给定的命名空间或编译单元直接包含具有合格扩展方法Mj的非泛型类型声明Ci,则这些扩展方法的集合是候选集。
- 如果在给定命名空间或编译单元中使用命名空间指令导入的命名空间直接包含具有合格扩展方法Mj的非泛型类型声明Ci,则这些扩展方法的集合是候选集。
- 如果在任何封闭的命名空间声明或编译单元中找不到候选集,则会发生编译时错误。