我更喜欢使用IEnumerable<object>
,因为LINQ扩展方法是在其上定义的,而不是IEnumerable
,以便我可以使用例如range.Skip(2)
。但是,我也更喜欢使用 IEnumerable
,因为T[]
可以隐式转换为IEnumerable
T
是引用类型还是值类型。对于后一种情况,不涉及拳击,这很好。结果,我可以做IEnumerable range = new[] { 1, 2, 3 }
.似乎不可能将两全其美结合起来。无论如何,当我需要应用 LINQ 方法时,我选择安定下来IEnumerable
并进行某种强制转换。
从这个SO线程中,我了解到range.Cast<object>()
能够完成这项工作。但它会产生性能开销,在我看来这是不必要的。我尝试执行像(IEnumerable<object>)range
这样的直接编译时强制转换。根据我的测试,它适用于引用元素类型,但不适用于值类型。有什么想法吗?
仅供参考,问题源于这个 GitHub 问题。我使用的测试代码如下:
static void Main(string[] args)
{
// IEnumerable range = new[] { 1, 2, 3 }; // won't work
IEnumerable range = new[] { "a", "b", "c" };
var range2 = (IEnumerable<object>)range;
foreach (var item in range2)
{
Console.WriteLine(item);
}
}
根据我的测试,它适用于参考元素类型,但不适用于 值类型。
正确。这是因为IEnumerable<out T>
是协变的,并且值类型不支持协方差/逆方差。
我开始知道这个范围。Cast(( 能够完成这项工作。但它 产生性能开销,在我看来这是不必要的。
IMO 如果您想要具有给定值类型集合的对象集合,则性能成本(由装箱带来(是不可避免的。使用非泛型IEnumerable
不会避免装箱,因为IEnumerable.GetEnumerator
提供了一个IEnumerator
,其.Current
属性返回object
。我宁愿总是使用IEnumerable<T>
而不是IEnumerable
。所以只需使用.Cast
方法,忘记拳击。
在反编译该扩展后,源代码显示如下:
public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source)
{
IEnumerable<TResult> enumerable = source as IEnumerable<TResult>;
if (enumerable != null)
return enumerable;
if (source == null)
throw Error.ArgumentNull("source");
return Enumerable.CastIterator<TResult>(source);
}
private static IEnumerable<TResult> CastIterator<TResult>(IEnumerable source)
{
foreach (TResult result in source)
yield return result;
}
这基本上除了首先IEnumerable<object>
之外什么也做不了。
你说:
根据我的测试,它适用于参考元素类型,但不适用于 值类型。
您是如何测试的?
尽管我真的不喜欢这种方法,但我知道可以提供类似于 LINQ-to-Objects 的工具集,该工具集可以直接在 IEnumerable
接口上调用,而无需强制强制转换到IEnumerable<object>
(糟糕:可能的装箱!(和不强制转换为IEnumerable<TFoo>
(更糟糕的是:我们需要知道并编写 TFoo!(。
但是,它是:
- 运行时不免费:它可能很重,我没有运行性能测试
- 对开发人员不是免费的:您实际上需要为 IEnumerable 编写所有这些类似 LINQ 的扩展方法(或找到一个执行此操作的库(
- 不简单:您需要仔细检查传入类型,并且需要小心许多可能的选项
- 不是预言机:给定一个实现 IEnumerable 但不实现
IEnumerable<T>
的集合,它只能抛出错误或静默地将其转换为IEnumerable<object>
- 不会总是有效:给定一个同时实现
IEnumerable<int>
和IEnumerable<string>
的集合,它根本不知道该怎么做;甚至放弃并投射到IEnumerable<object>
在这里听起来也不对
下面是 的示例。净4+:
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 到对象.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 或远程处理而失败(。然而。。我认为满足于IEnumerable<T>
是一个更好的选择。让我们把普通的旧IEnumerable
留给实际上没有其他选择的特殊情况。
..哦。。我实际上没有调查上面的代码是否正确、快速、安全、资源节约、懒惰评估等。
IEnumerable<T>
是一个通用接口。只要你只处理编译时已知的泛型和类型,使用IEnumerable<object>
就没有意义 - 要么使用 IEnumerable<int>
要么使用 IEnumerable<T>
,这完全取决于您是在编写泛型方法,还是已经知道正确类型的方法。不要试图找到一个 IEnumerable 来适合它们 - 首先使用正确的一个 - 这是不可能的是非常罕见的,而且大多数时候,这只是糟糕的对象设计的结果。
IEnumerable<int>
不能转换为IEnumerable<object>
的原因可能有些令人惊讶,但实际上非常简单 - 值类型不是多态的,因此它们不支持协方差。不要误会 - IEnumerable<string>
没有实现IEnumerable<object>
- 您可以将IEnumerable<string>
转换为IEnumerable<object>
的唯一原因是IEnumerable<T>
是协变的。
这只是一个"令人惊讶但显而易见"的有趣案例。这很令人惊讶,因为int
源于object
,对吧?然而,这是显而易见的,因为int
并不是真正从object
派生出来的,即使它可以通过一个叫做boxing的过程被投射到一个object
,这个过程会创建一个"真正的对象派生int"。