在C#中,如何从给定数组中获取通用枚举数?
在下面的代码中,MyArray
是一个MyType
对象数组。我想以MyIEnumerator
所示的方式获得,但似乎我获得了一个空的枚举器(尽管我已确认MyArray.Length > 0
)。
MyType[] MyArray = ... ;
IEnumerator<MyType> MyIEnumerator = MyArray.GetEnumerator() as IEnumerator<MyType>;
在C#中,如何从给定数组中获取通用枚举数?
在下面的代码中,MyArray
是一个MyType
对象数组。我想以MyIEnumerator
所示的方式获得,但似乎我获得了一个空的枚举器(尽管我已确认MyArray.Length > 0
)。
MyType[] MyArray = ... ;
IEnumerator<MyType> MyIEnumerator = MyArray.GetEnumerator() as IEnumerator<MyType>;
Answers:
适用于2.0以上版本:
((IEnumerable<MyType>)myArray).GetEnumerator()
适用于3.5以上版本(花式LINQy,效率略低):
myArray.Cast<MyType>().GetEnumerator() // returns IEnumerator<MyType>
LINQ/Cast
具有完全不同的运行时行为,因为数组的每个元素都将通过一组额外的MoveNext
和Current
枚举器往返传递,如果数组很大,这可能会影响性能。无论如何,通过首先获得适当的枚举数(即,使用我的答案中所示的两种方法之一),可以完全轻松地避免此问题。
myArray.Cast<MyType>().GetEnumerator()
在最内层的循环中使用,即使是很小的阵列,也可能使您的速度大大降低。
您可以自己决定是否强制转换足够丑陋,以至于不需要进行额外的库调用:
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[]
选择非通用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
?
由于我不喜欢投射,因此需要进行一些更新:
your_array.AsEnumerable().GetEnumerator();
your_array.AsEnumerable()
不会首先编译,因为AsEnumerable()
只能在实现类型的实例上使用IEnumerable
。
为了使其尽可能干净,我希望让编译器完成所有工作。没有强制转换(因此它实际上是类型安全的)。没有使用第三方库(System.Linq)(没有运行时开销)。
public static IEnumerable<T> GetEnumerable<T>(this T[] arr)
{
return arr;
}
//并使用代码:
String[] arr = new String[0];
arr.GetEnumerable().GetEnumerator()
这利用了一些使所有内容保持干净的编译器魔术。
要注意的另一点是,我的答案是唯一可以进行编译时检查的答案。
对于任何其他解决方案,如果“ arr”的类型发生更改,则调用代码将编译,并在运行时失败,从而导致运行时错误。
我的回答将导致代码无法编译,因此我在代码中发布错误的机会较小,因为这将向我发出信号,表明我使用了错误的类型。
Foo[]
实现IEnumerable<Foo>
,但是如果更改了,则在编译时将不会检测到。显式强制转换从不证明编译时。而是将分配/返回数组作为IEnumerable <Foo>使用隐式转换,这是编译时证明的。
var foo = (int)new object()
。它编译得很好,并在运行时崩溃。
当然,您可以做的只是为数组实现自己的通用枚举器。
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 <T>的.NET实现。当然,在这种情况下值得努力的话,您只应该这样做。在大多数情况下不是。