LINQ中自定义Select()扩展方法的正确使用方式
问题解答
一、数组转Enumerable再转回数组是否属于代码坏味道?
首先,AsEnumerable()并没有创建新的集合对象——数组本身就实现了IEnumerable<S>,这个方法只是改变了变量的静态类型,从S[]变为IEnumerable<S>,性能上几乎没有额外开销,所以单纯这一步不算代码坏味道。
但你的实现本质上还是依赖原生LINQ的Select再转数组,和直接写array.Select(...).ToArray()没有本质区别,只是做了一层封装。如果想真正避免中间枚举对象的开销,更优的方式是直接遍历数组生成新数组,跳过LINQ的中间迭代器:
static class EnumerableExtensions { public static R[] Select<S, R>(this S[] self, Func<S, R> func) { var result = new R[self.Length]; for (int i = 0; i < self.Length; i++) { result[i] = func(self[i]); } return result; } }
这种实现完全没有中间枚举对象,效率更高,也更贴合你“直接生成数组”的初衷。
二、如何让C#明确调用不同版本的Select?
1. 调用自定义版本(返回数组)
当直接在S[]上调用Select时,因为自定义扩展方法的参数更具体(S[]比IEnumerable<S>匹配度更高),C#会优先选择你的自定义版本:
string[] thisIsArray = array.Select(a => a.Property); // 调用自定义版本 var whatIsThis = array.Select(a => a.Property); // 推断类型为string[],调用自定义版本
2. 调用原生LINQ版本(返回IEnumerable<string>)
如果想强制调用原生的Select,需要先把数组的静态类型转为IEnumerable<S>,引导编译器匹配原生扩展方法:
// 显式声明类型时,先转IEnumerable再调用 IEnumerable<string> thisIsEnumerable = array.AsEnumerable().Select(a => a.Property); // 使用var时,同样先转类型来改变推断结果 var whatIsThis = array.AsEnumerable().Select(a => a.Property); // 推断类型为IEnumerable<string>
需要注意:如果直接写IEnumerable<string> thisIsEnumerable = array.Select(...),C#还是会调用自定义版本——因为string[]可以隐式转换为IEnumerable<string>,编译器依然会优先选择参数更具体的自定义方法,必须通过AsEnumerable()切换静态类型才能触发原生版本的调用。
内容的提问来源于stack exchange,提问作者Konrad Viltersten
相关产品推荐
相关产品推荐

