在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>;
适用于2.0及以上版本:
((IEnumerable<MyType>)myArray).GetEnumerator()
适用于3.5+(类似LINQ,效率略低):
myArray.Cast<MyType>().GetEnumerator() // returns IEnumerator<MyType>
LINQ/Cast
具有非常不同的运行时行为,因为数组的每个元素都将通过额外的MoveNext
和Current
枚举器往返传递,如果数组很大,这可能会影响性能。无论如何,可以通过首先获取正确的枚举器(即使用我答案中展示的两种方法之一)完全轻松地避免问题。 - Glenn SlaydenmyArray.Cast<MyType>().GetEnumerator()
,即使对于微小的数组,它也可能会显著地减慢您的速度。 - Evgeniy Berezovskyclass
)还是值类型(struct
)。当每个值类型大于十几个字节时,额外的按值洗牌可以迅速降低LINQ管道的性能。 - Glenn Slayden您可以自行决定是否需要调用一个额外的库来处理类型转换是否太丑陋:
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
——一个当前被标记为“sealed”的内部类——因为这将允许(协变)泛型枚举器默认返回?((IEnumerable<int>)arr)
中使用了额外的括号,但在 (IEnumerator<int>)arr
中只使用了一组括号,这是有原因的吗? - David Klempfneras
关键字的IL代码是否比强制转换更优? - Rabadash8820as
将直接映射到它们自己的特定IL指令——castclass
与isinst
。虽然根据其定义,“as”似乎更具有存在性的“作用”,但这两者之间的实际性能差异在任何情况下都不会是可测量的。 - Glenn Slayden因为我不喜欢强制转换类型,所以进行了一点更新:
your_array.AsEnumerable().GetEnumerator();
your_array.AsEnumerable()
在第一次编译时就无法通过,因为AsEnumerable()
只能用于实现IEnumerable
的类型的实例。 - David Klempfner public static IEnumerable<T> GetEnumerable<T>(this T[] arr)
{
return arr;
}
// 为了使用该代码:
String[] arr = new String[0];
arr.GetEnumerable().GetEnumerator()
Foo[]
实现了IEnumerable <Foo>
,但如果这个情况有所改变,编译时将无法检测到变化。显式转换永远不能编译时证明是正确的。相反,将数组分配/返回给IEnumerable<Foo>
使用的是隐式转换,这是可以编译时证明是正确的。 - Toxantronvar foo = (int)new object()
。它可以编译成功,但在运行时会崩溃。 - ToxantronYourArray.OfType<StringId>().GetEnumerator();
这段代码可能会表现更好,因为它只需要检查类型,而不是进行强制转换。
OfType<..type..>()
时,您必须明确指定类型 - 至少在我的 double[][]
情况下。 - M. Mimpen MyType[] arr = { new MyType(), new MyType(), new MyType() };
IEnumerable<MyType> enumerable = arr;
IEnumerator<MyType> en = enumerable.GetEnumerator();
foreach (MyType item in enumerable)
{
}
自从 @Mehrdad Afshari 的回答后,我为此编写了扩展方法:
public static IEnumerator<T> GetEnumerator<T>(this T[] array) {
return (IEnumerator<T>)array.GetEnumerator();
}
那么你可以像这样实现IEnumerable:
public class MulticoloredLine : IEnumerable<ColoredLine> {
private ColoredLine[] coloredLines;
#region IEnumerable<ColoredLine>
public IEnumerator<ColoredLine> GetEnumerator() =>
coloredLines.GetEnumerator<ColoredLine>();
IEnumerator IEnumerable.GetEnumerator() => coloredLines.GetEnumerator();
#endregion
}
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实现。当然,只有在值得付出努力的情况下才应该这样做。在大多数情况下,这是不必要的。