尽管我不太喜欢这种方法,但我知道可以提供一个类似于LINQ-to-Objects的工具集,可以直接在IEnumerable接口上调用,而不需要强制转换为IEnumerable(不好:可能会出现装箱!)并且不需要转换为IEnumerable(更糟糕的是:我们需要知道并编写TFoo!)。然而,它是:
- 运行时不免费:它可能很重,我没有运行性能测试
- 开发者不免费:您实际上需要编写所有那些针对IEnumerable的类似LINQ的扩展方法(或找到一个库来执行此操作)
- 不简单:您需要仔细检查传入类型,并小心处理许多可能的选项
- 不是神谕:给定一个实现了IEnumerable但未实现的集合,它只能抛出错误或将其静默地转换为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)
{
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)
);
}
else
return items.Cast<object>()
.doSomething_X()
.doSomething_X();
}
private static IEnumerable<T> doSomething_X<T>(this IEnumerable<T> valitems)
{
Console.WriteLine("I got <{1}>: {0}", valitems.Count(), typeof(T));
return valitems;
}
}
是的,这很傻。我将它们链接了四次(2个外部x2个内部),只是为了显示类型信息在后续调用中不会丢失。重点是要显示“入口点”采用非通用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
,并进行一些魔法以正确调用它而不实际转换(可能再次使用反射)。
然而,似乎可以在非泛型 IEnumerable 后面隐藏的对象上“直接”使用类似 Linq 的东西。这要归功于隐藏在 IEnumerable 后面的对象仍然具有自己的完整类型信息(是的,这个假设可能会在 COM 或 Remoting 中失败)。不过..我认为选择 IEnumerable 是更好的选择。让我们把普通的 IEnumerable 留给真正没有其他选择的特殊情况。
哦,我其实没有调查上面的代码是否正确、快速、安全、资源节约、惰性评估等。
IEnumerable
?为什么不直接用IEnumerable<int> range = new[] {1, 2, 3};
呢?顺便问一下,非泛型的IEnumerable
会进行装箱吗?如果你在一个非泛型的IEnumerable
上使用foreach
,你得到的是一个object
。然而,另一方面,泛型 的可枚举类型 (IEnumerable<T>
) 不会进行装箱。 - CorakIEnumerable
上定居,而不是IEnumerable<object>
。现在,我只是等待团队对这个提议的回应 :) - LingxiIEnumerable<T>
。所以,如果你现在有一个像doStuff(IEnumerable items){/* ... */}
的方法,你可以将其更改为doStuff<T>(IEnumerable<T> items){/* ... */}
,然后你就有了一个实现,可以用类型安全(和非装箱)的方式处理int[]
、List<string>
或IEnumerable<YourClass>
。 - Corak