从数组获取通用枚举器


91

在C#中,如何从给定数组中获取通用枚举数?

在下面的代码中,MyArray是一个MyType对象数组。我想以MyIEnumerator所示的方式获得,但似乎我获得了一个空的枚举器(尽管我已确认MyArray.Length > 0)。

MyType[] MyArray = ... ;
IEnumerator<MyType> MyIEnumerator = MyArray.GetEnumerator() as IEnumerator<MyType>;

出于好奇,为什么要获得枚举数?
David Klempfner

1
在我的情况下,@ Backwards_Dave在单线程环境中有一个文件列表,每个文件必须以异步方式处理一次。我可以使用索引来增加,但可枚举的对象更酷:)
hpaknia

Answers:


102

适用于2.0以上版本:

((IEnumerable<MyType>)myArray).GetEnumerator()

适用于3.5以上版本(花式LINQy,效率略低):

myArray.Cast<MyType>().GetEnumerator()   // returns IEnumerator<MyType>

3
LINQy代码实际上为Cast方法的结果返回一个枚举数,而不是该数组的枚举数……
Guffa

1
Guffa:由于枚举器仅提供只读访问权限,因此在用法上没有太大区别。
Mehrdad Afshari

8
就像@Mehrdad暗示的那样,using LINQ/Cast具有完全不同的运行时行为,因为数组的每个元素都将通过一组额外的MoveNextCurrent枚举器往返传递,如果数组很大,这可能会影响性能。无论如何,通过首先获得适当的枚举数(即,使用我的答案中所示的两种方法之一),可以完全轻松地避免此问题。
Glenn Slayden '16

7
首先是更好的选择!不要有人被完全多余的“花哨的LINQy”版本欺骗自己。
亨农

@GlennSlayden不仅对于大型数组来说很慢,对于任何大小的数组来说也很慢。因此,如果myArray.Cast<MyType>().GetEnumerator()在最内层的循环中使用,即使是很小的阵列,也可能使您的速度大大降低。
Evgeniy Berezovsky '18

54

您可以自己决定是否强制转换足够丑陋,以至于不需要进行额外的库调用:

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
David Klempfner

1
@Backwards_Dave是的,这就是使顶部的正确示例与底部的错误示例有所不同的原因。检查运算符优先级的的C#一元“中投”操作符。
Glenn Slayden '19

24

由于我不喜欢投射,因此需要进行一些更新:

your_array.AsEnumerable().GetEnumerator();

AsEnumerable()也可以进行转换,因此您仍在进行转换:)也许可以使用your_array.OfType <T>()。GetEnumerator();。
MladenB。18年

1
区别在于它是在编译时完成的隐式转换,而不是在运行时完成的显式转换。因此,如果类型错误,将产生编译时错误,而不是运行时错误。
汉克·舒尔茨

@HankSchultz如果类型错误,则your_array.AsEnumerable()不会首先编译,因为AsEnumerable()只能在实现类型的实例上使用IEnumerable
David Klempfner '19

5

为了使其尽可能干净,我希望让编译器完成所有工作。没有强制转换(因此它实际上是类型安全的)。没有使用第三方库(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

1
@ t3chb0t不,他们不是。在运行时进行工作是因为我们知道Foo[]实现IEnumerable<Foo>,但是如果更改了,则在编译时将不会检测到。显式强制转换从不证明编译时。而是将分配/返回数组作为IEnumerable <Foo>使用隐式转换,这是编译时证明的。
Toxantron

@Toxantron除非要尝试转换的类型实现IEnumerable,否则不会编译为IEnumerable <T>的显式转换。当您说“但如果改变了”时,您能否举例说明您的意思?
David Klempfner '19

当然可以编译。那就是InvalidCastException的目的。您的编译器可能会警告您,某些强制转换不起作用。尝试var foo = (int)new object()。它编译得很好,并在运行时崩溃。
Toxantron

2

YourArray.OfType()。GetEnumerator();

可能会好一些,因为它只需要检查类型,而不必强制转换。


你必须明确地指定使用时型OfType<..type..>()-在我的情况至少double[][]
M. Mimpen

0
    MyType[] arr = { new MyType(), new MyType(), new MyType() };

    IEnumerable<MyType> enumerable = arr;

    IEnumerator<MyType> en = enumerable.GetEnumerator();

    foreach (MyType item in enumerable)
    {

    }

您能解释一下上面的代码将执行什么吗?
Phani

这应该是更高的选票!使用编译器的隐式强制转换是最干净,最简单的解决方案。
Toxantron

-1

当然,您可以做的只是为数组实现自己的通用枚举器。

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实现。当然,在这种情况下值得努力的话,您只应该这样做。在大多数情况下不是。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.