从数组中获取通用枚举器
Posted
技术标签:
【中文标题】从数组中获取通用枚举器【英文标题】:obtain generic enumerator from an array 【发布时间】:2009-08-13 15:21:24 【问题描述】:在 C# 中,如何从给定数组中获取泛型枚举数?
在下面的代码中,MyArray
是 MyType
对象的数组。我想以所示的方式获得MyIEnumerator
,
但似乎我得到了一个空的枚举器(虽然我已经确认MyArray.Length > 0
)。
MyType[] MyArray = ... ;
IEnumerator<MyType> MyIEnumerator = MyArray.GetEnumerator() as IEnumerator<MyType>;
【问题讨论】:
只是出于好奇,为什么要获取枚举器? @Backwards_Dave 在我的情况下,在单线程环境中,有一个文件列表,每个文件都必须以异步方式处理一次。我可以使用索引来增加,但枚举更酷:) 【参考方案1】:适用于 2.0+:
((IEnumerable<MyType>)myArray).GetEnumerator()
适用于 3.5+(花哨的 LINQy,效率稍低):
myArray.Cast<MyType>().GetEnumerator() // returns IEnumerator<MyType>
【讨论】:
LINQy 代码实际上返回的是 Cast 方法结果的枚举器,而不是数组的枚举器... Guffa:由于枚举器只提供只读访问权限,因此在使用方面并没有太大区别。 正如@Mehrdad 所暗示的,使用LINQ/Cast
具有完全不同的运行时行为,因为数组的每个元素 将通过一组额外的MoveNext
和Current
枚举器往返,如果数组很大,这可能会影响性能。无论如何,通过首先获得适当的枚举器(即使用我的答案中显示的两种方法之一),可以完全轻松地避免该问题。
第一个是更好的选择!不要让自己被完全多余的“花式LINQy”版本欺骗。
@GlennSlayden 不仅对于大型数组来说很慢,对于任何大小的数组来说都很慢。因此,如果您在最里面的循环中使用 myArray.Cast<MyType>().GetEnumerator()
,即使对于很小的数组,它也可能会显着减慢您的速度。【参考方案2】:
您可以自己决定强制转换是否丑到足以保证一个无关的库调用:
int[] arr;
IEnumerator<int> Get1()
return ((IEnumerable<int>)arr).GetEnumerator(); // <-- 1 non-local call
// ldarg.0
// ldfld int32[] foo::arr
// castclass System.Collections.Generic.IEnumerable`1<int32>
// callvirt instance class System.Collections.Generic.IEnumerator`1<!0> System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()
IEnumerator<int> Get2()
return arr.AsEnumerable().GetEnumerator(); // <-- 2 non-local calls
// ldarg.0
// ldfld int32[] foo::arr
// call class System.Collections.Generic.IEnumerable`1<!!0> System.Linq.Enumerable::AsEnumerable<int32>(class System.Collections.Generic.IEnumerable`1<!!0>)
// callvirt instance class System.Collections.Generic.IEnumerator`1<!0> System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()
为了完整起见,还应注意以下内容是不正确的——并且会在运行时崩溃——因为T[]
选择 non-generic IEnumerable
接口作为其默认值(即非显式)实现GetEnumerator()
。
IEnumerator<int> NoGet() // error - do not use
return (IEnumerator<int>)arr.GetEnumerator();
// ldarg.0
// ldfld int32[] foo::arr
// callvirt instance class System.Collections.IEnumerator System.Array::GetEnumerator()
// castclass System.Collections.Generic.IEnumerator`1<int32>
谜团在于,为什么SZGenericArrayEnumerator<T>
不从SZArrayEnumerator
继承——一个当前标记为“密封”的内部类——因为这将允许默认返回(协变)泛型枚举数?
【讨论】:
您在((IEnumerable<int>)arr)
中使用了额外的括号,但在(IEnumerator<int>)arr
中只使用了一组括号,这是有原因的吗?
@Backwards_Dave 是的,这就是顶部正确示例和底部不正确示例之间的区别。检查 C# 一元“强制转换”运算符的operator precedence。
@GlennSlayden as
的 IL 会比强制转换更优化吗?
"Casting" 与 as
将各自直接映射到它们自己的特定 IL 指令 -- castclass
与 isinst
相比。尽管从定义上看,“as”直觉上似乎在做更多存在的“工作”,但这两者之间的实际性能差异可能在任何情况下都无法衡量。【参考方案3】:
由于我不喜欢选角,所以稍微更新一下:
your_array.AsEnumerable().GetEnumerator();
【讨论】:
AsEnumerable() 也进行强制转换,因此您仍在进行强制转换:) 也许您可以使用 your_array.OfTypeyour_array.AsEnumerable()
将不会首先编译,因为AsEnumerable()
只能用于实现IEnumerable
的类型的实例。【参考方案4】:
为了让它尽可能干净,我喜欢让编译器完成所有工作。没有强制转换(因此它实际上是类型安全的)。不使用第三方库 (System.Linq)(无运行时开销)。
public static IEnumerable<T> GetEnumerable<T>(this T[] arr)
return arr;
// 并使用代码:
String[] arr = new String[0];
arr.GetEnumerable().GetEnumerator()
这利用了一些保持一切干净的编译器魔法。
需要注意的另一点是我的答案是唯一会进行编译时检查的答案。
对于任何其他解决方案,如果“arr”的类型发生更改,则调用代码将编译,并在运行时失败,从而导致运行时错误。
我的回答将导致代码无法编译,因此我在代码中出现错误的可能性较小,因为它会向我表明我使用了错误的类型。
【讨论】:
GlennSlayden 和 MehrdadAfshari 所示的铸造也是编译时证明。 @t3chb0t 不,他们不是。运行时的工作因为我们知道Foo[]
实现了IEnumerable<Foo>
,但是如果它发生了变化,它将不会在编译时被检测到。显式转换永远不是编译时证明。相反,分配/返回数组作为 IEnumerablevar foo = (int)new object()
。它编译得很好并在运行时崩溃。【参考方案5】:
YourArray.OfType().GetEnumerator();
性能可能会好一点,因为它只需要检查类型,而不是强制转换。
【讨论】:
你必须在使用OfType<..type..>()
时明确指定类型——至少在我的double[][]
的情况下【参考方案6】:
MyType[] arr = new MyType(), new MyType(), new MyType() ;
IEnumerable<MyType> enumerable = arr;
IEnumerator<MyType> en = enumerable.GetEnumerator();
foreach (MyType item in enumerable)
【讨论】:
你能解释一下上面的代码会执行什么吗> 这应该投票得更高!使用编译器的隐式转换是最干净和最简单的解决方案。【参考方案7】:当然,您可以做的只是为数组实现自己的通用枚举器。
using System.Collections;
using System.Collections.Generic;
namespace SomeNamespace
public class ArrayEnumerator<T> : IEnumerator<T>
public ArrayEnumerator(T[] arr)
collection = arr;
length = arr.Length;
private readonly T[] collection;
private int index = -1;
private readonly int length;
public T Current get return collection[index];
object IEnumerator.Current get return Current;
public bool MoveNext() index++; return index < length;
public void Reset() index = -1;
public void Dispose() /* Nothing to dispose. */
这或多或少等于 Glenn Slayden 提到的 SZGenericArrayEnumerator
【讨论】:
以上是关于从数组中获取通用枚举器的主要内容,如果未能解决你的问题,请参考以下文章
Kotlin,Spring Boot,JPA - 获取通用枚举的值 (E.valueOf(stringValue))