从 IEnumerable 转换为 IEnumerable<object>
Posted
技术标签:
【中文标题】从 IEnumerable 转换为 IEnumerable<object>【英文标题】:Cast from IEnumerable to IEnumerable<object> 【发布时间】:2016-07-19 07:21:58 【问题描述】:我更喜欢使用IEnumerable<object>
,因为LINQ扩展方法是在上面定义的,而不是IEnumerable
,所以我可以使用,例如range.Skip(2)
。但是,我也更喜欢使用IEnumerable
,因为无论T
是引用类型还是值类型,T[]
都可以隐式转换为IEnumerable
。对于后一种情况,不涉及拳击,这很好。结果,我可以做到IEnumerable range = new[] 1, 2, 3
。似乎不可能将两全其美的东西结合起来。无论如何,我选择在IEnumerable
安顿下来,并在需要应用 LINQ 方法时进行某种类型的转换。
从this SO 线程,我知道range.Cast<object>()
能够胜任这项工作。但它会产生性能开销,我认为这是不必要的。我尝试执行像(IEnumerable<object>)range
这样的直接编译时转换。根据我的测试,它适用于引用元素类型,但不适用于值类型。有什么想法吗?
仅供参考,问题源于this 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<object>
,还是IEnumerable<T>
?如果是前者,你为什么用<object>
而不是<T>
?
@Maarten 是的,我的意思是使用IEnumerable<object>
,因为我需要处理一般情况,并支持不同的元素类型。
你为什么要和IEnumerable
一起工作呢?为什么不改为IEnumerable<int> range = new[] 1, 2, 3 ;
? - 顺便提一句。 IEnumerable
确实框,对吗?如果您在(非通用)IEnumerable
上使用foreach
,您将获得object
。另一方面,一个 generic 可枚举 (IEnumerable<T>
) 没有 not 框。
@Corak 正如对 Maarten 评论的回复所说,我的意思是使用IEnumerable<object>
,因为我需要处理一般情况,并支持不同的元素类型。 关于IEnumerable
,我认为拳击在每次枚举时都会发生,但不是在分配时发生。查看更新问题中的测试代码。
仍然不确定我是否理解。你说你想处理“一般情况”。那么“一般情况”将是IEnumerable<T>
。因此,如果您现在有一个类似doStuff(IEnumerable items)/* ... */
的方法,您可以将其更改为doStuff<T>(IEnumerable<T> items)/* ... */
,然后您就有一个 实现,您可以在其中处理int[]
、List<string>
或IEnumerable<YourClass>
,但在一个类型安全(和非装箱)方式。
【参考方案1】:
根据我的测试,它适用于参考元素类型,但不适用于 值类型。
正确。这是因为IEnumerable<out T>
是协变的,而co-variance/contra-variance is not supported for value types。
我知道 range.Cast() 能够完成这项工作。但它 会产生性能开销,我认为这是不必要的。
IMO 如果您想要一组具有给定值类型的对象集合,则性能成本(由装箱带来)是不可避免的。使用非泛型IEnumerable
不会避免装箱,因为IEnumerable.GetEnumerator
提供了IEnumerator
,其.Current
属性返回object
。我宁愿总是使用IEnumerable<T>
而不是IEnumerable
。所以只需使用.Cast
方法,忘记装箱。
【讨论】:
【参考方案2】:反编译该扩展后,源代码显示如下:
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>
之外什么都不做。
你说:
根据我的测试,它适用于参考元素类型,但不适用于 值类型。
你是如何测试的?
【讨论】:
请注意,通过IEnumerable<out T>
的协方差,这意味着如果您有一个IEnumerable<X>
,其中X
是一个引用类型,那么.Cast<object>()
将让同一个集合实例通过un -改变。 IE。实际上,它将是对基础集合对象的简单引用转换。对于值类型X
,.Cast<object>()
的使用必然会贯穿所有项目并将它们装箱(懒惰,就像.Select(x => (object)x)
)。【参考方案3】:
尽管我真的不喜欢这种方法,但我知道可以提供一个类似于 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.. 我实际上并没有调查上面的代码是否正确、快速、安全、资源节约、惰性评估等。
【讨论】:
哇~我肯定很惊讶。 (@_@)【参考方案4】:IEnumerable<T>
是一个通用接口。只要您只处理编译时已知的泛型和类型,使用IEnumerable<object>
就没有意义 - 使用IEnumerable<int>
或IEnumerable<T>
,完全取决于您是否正在编写泛型方法,或者一个已经知道正确类型的地方。不要试图找到适合所有这些的 IEnumerable - 首先使用正确的 - 这是不可能的,这是非常罕见的,而且大多数情况下,这只是糟糕的对象设计的结果。
IEnumerable<int>
不能转换为 IEnumerable<object>
的原因可能有点令人惊讶,但实际上非常简单——值类型不是多态的,因此它们不支持协变。不要误会 - IEnumerable<string>
没有实现 IEnumerable<object>
- 你可以转换 IEnumerable<string>
到 IEnumerable<object>
的唯一原因是 IEnumerable<T>
是 co -变体。
这只是一个“令人惊讶但显而易见”的有趣案例。令人惊讶的是,int
源自 object
,对吧?然而,很明显,因为int
并不是真正 派生自object
,即使它可以通过称为装箱的过程转换 为object
,它创建了一个“真正的对象派生的 int”。
【讨论】:
我必须使用IEnumerable<object>
,因为我需要支持不同的元素类型(即运行时多态)。
@Lingxi 如果是这种情况,请使用IEnumerable<object>
作为最后的手段,并通过.Cast<object>
获得它。对于引用类型,它将执行简单的强制转换(因为它们是协变的),对于值类型,它将执行与 IEnumerable
相同的操作。以上是关于从 IEnumerable 转换为 IEnumerable<object>的主要内容,如果未能解决你的问题,请参考以下文章
从 IEnumerable 转换为 IEnumerable<object>
无法从“System.Collections.IList”转换为“System.Collections.Generic.IEnumerable<T>”
带有 IEnumerable<child> 成员的 ViewModel 在 POST 上未正确绑定 [重复]