尽管我真的不喜欢这种方法,但我知道可以提供一个类似于 LINQ-to-Objects 的工具集,它可以直接在 IEnumerable 接口上调用,而无需强制转换为 IEnumerable<object>(不好:可能的拳击!)并且没有投射到IEnumerable<TFoo>(更糟糕的是:我们需要知道并编写TFoo!)。
但是,它是:
- 运行时不是免费的:它可能很重,我没有运行性能测试
- 对开发人员来说不是免费的:您实际上需要为 IEnumerable 编写所有类似 LINQ 的扩展方法(或找到一个库)
- 不简单:您需要仔细检查传入的类型,并且需要小心许多可能的选项
- 不是预言机:给定一个实现 IEnumerable 但未实现
IEnumerable<T> 的集合,它只能抛出错误或静默地将其强制转换为 IEnumerable<object>
- 并不总是有效:给定一个同时实现
IEnumerable<int> 和IEnumerable<string> 的集合,它根本不知道该做什么;即使放弃并投给IEnumerable<object> 听起来也不对
以下是 .Net4+ 的示例:
using System;
using System.Linq;
using System.Collections.Generic;
class Program
{
public static void Main()
{
Console.WriteLine("List<int>");
new List<int> { 1, 2, 3 }
.DoSomething()
.DoSomething();
Console.WriteLine("List<string>");
new List<string> { "a", "b", "c" }
.DoSomething()
.DoSomething();
Console.WriteLine("int[]");
new int[] { 1, 2, 3 }
.DoSomething()
.DoSomething();
Console.WriteLine("string[]");
new string[] { "a", "b", "c" }
.DoSomething()
.DoSomething();
Console.WriteLine("nongeneric collection with ints");
var stack = new System.Collections.Stack();
stack.Push(1);
stack.Push(2);
stack.Push(3);
stack
.DoSomething()
.DoSomething();
Console.WriteLine("nongeneric collection with mixed items");
new System.Collections.ArrayList { 1, "a", null }
.DoSomething()
.DoSomething();
Console.WriteLine("nongeneric collection with .. bits");
new System.Collections.BitArray(0x6D)
.DoSomething()
.DoSomething();
}
}
public static class MyGenericUtils
{
public static System.Collections.IEnumerable DoSomething(this System.Collections.IEnumerable items)
{
// check the REAL type of incoming collection
// if it implements IEnumerable<T>, we're lucky!
// we can unwrap it
// ...usually. How to unwrap it if it implements it multiple times?!
var ietype = items.GetType().FindInterfaces((t, args) =>
t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>),
null).SingleOrDefault();
if (ietype != null)
{
return
doSomething_X(
doSomething_X((dynamic)items)
);
// .doSomething_X() - and since the compile-time type is 'dynamic' I cannot chain
// .doSomething_X() - it in normal way (despite the fact it would actually compile well)
// `dynamic` doesn't resolve extension methods!
// it would look for doSomething_X inside the returned object
// ..but that's just INTERNAL implementation. For the user
// on the outside it's chainable
}
else
// uh-oh. no what? it can be array, it can be a non-generic collection
// like System.Collections.Hashtable .. but..
// from the type-definition point of view it means it holds any
// OBJECTs inside, even mixed types, and it exposes them as IEnumerable
// which returns them as OBJECTs, so..
return items.Cast<object>()
.doSomething_X()
.doSomething_X();
}
private static IEnumerable<T> doSomething_X<T>(this IEnumerable<T> valitems)
{
// do-whatever,let's just see it being called
Console.WriteLine("I got <{1}>: {0}", valitems.Count(), typeof(T));
return valitems;
}
}
是的,这很愚蠢。我将它们链接了四次 (2outsidex2inside) 只是为了表明类型信息在后续调用中不会丢失。关键是要表明“入口点”采用非泛型IEnumerable,并且<T> 可以在任何可能的地方解析。您可以轻松地修改代码,使其成为普通的 LINQ-to-Objects .Count() 方法。同样,也可以编写所有其他操作。
此示例使用dynamic 让平台为 IEnumerable 解析最窄的 T,如果可能的话(我们需要首先确保这一点)。如果没有dynamic(即.Net2.0),我们需要通过反射调用dosomething_X,或者将它实现为dosomething_refs<T>():where T:class+dosomething_vals<T>():where T:struct 并做一些魔术来正确调用它而无需实际转换(可能是反射,再次)。
尽管如此,您似乎可以让类似 linq 的东西“直接”处理隐藏在非泛型 IEnumerable 后面的东西。这一切都归功于隐藏在 IEnumerable 后面的对象仍然拥有自己的完整类型信息(是的,COM 或 Remoting 的假设可能会失败)。但是.. 我认为选择IEnumerable<T> 是一个更好的选择。让我们将普通的旧 IEnumerable 留给确实没有其他选择的特殊情况。
..oh.. 我实际上并没有调查上面的代码是否正确、快速、安全、资源节约、惰性评估等。