配列から汎用列挙子を取得する


91

C#では、特定の配列から汎用列挙子をどのように取得しますか?

以下のコードでMyArrayは、はMyTypeオブジェクトの配列です。MyIEnumerator表示されている方法で取得したいのですが、空の列挙子を取得しているようです(確認済みですMyArray.Length > 0)。

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

好奇心から、なぜ列挙子を取得したいのですか?
David Klempfner、

1
@Backwards_Dave私の場合、シングルスレッド環境では、非同期の方法で各ファイルを1回処理する必要があるファイルのリストがあります。インデックスを使用して増加させることもできますが、列挙可能なものはより涼しいです:)
hpaknia

回答:


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実行時の動作はまったく異なります。配列が巨大な場合、これはパフォーマンスに影響を与える可能性があります。いずれの場合でも、最初に適切な列挙子を取得することで(つまり、私の回答に示されている2つの方法のいずれかを使用して)、問題を完全かつ簡単に回避できます。MoveNextCurrent
Glenn Slayden

7
最初はより良いオプションです!完全に不要な「ファンシーLINQy」バージョンにだまされてはいけません。
henon

@GlennSlayden巨大な配列の場合だけでなく、あらゆるサイズの配列の場合も遅くなります。したがってmyArray.Cast<MyType>().GetEnumerator()、最も内側のループで使用すると、小さな配列であっても大幅に速度が低下する可能性があります。
Evgeniy Berezovsky

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[]選択するためです。IEnumerableGetEnumerator()

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)たのに、1組のブラケットのみを使用した理由はあります(IEnumerator<int>)arrか?
David Klempfner、

1
@Backwards_Daveはい、それが上にある正しい例と下にある正しくない例を区別するものです。C#の単項「キャスト」演算子の演算子の優先順位を確認します。
Glenn Slayden

24

私はキャストが好きではないので、少し更新します:

your_array.AsEnumerable().GetEnumerator();

AsEnumerable()もキャストを行うので、キャストを続けます:)おそらくyour_array.OfType <T>()。GetEnumerator();を使用できます。
Mladen B.

1
違いは、実行時に行われる明示的なキャストではなく、コンパイル時に行われる暗黙的なキャストであることです。したがって、型が間違っていると、実行時エラーではなくコンパイル時エラーが発生します。
ハンクシュルツ

@HankSchultzは、型が間違っている場合、を実装する型のインスタンスでのみ使用できるyour_array.AsEnumerable()ため、そもそもコンパイルされません。AsEnumerable()IEnumerable
David Klempfner、

5

できるだけクリーンにするために、コンパイラーにすべての作業を任せたいと思います。キャストはありません(そのため、実際にはタイプセーフです)。サードパーティのライブラリ(System.Linq)は使用されません(実行時のオーバーヘッドはありません)。

    public static IEnumerable<T> GetEnumerable<T>(this T[] arr)
    {
        return arr;
    }

//そしてコードを使用するには:

    String[] arr = new String[0];
    arr.GetEnumerable().GetEnumerator()

これは、すべてをクリーンに保つコンパイラーの魔法を利用しています。

もう1つ注意すべき点は、私の答えはコンパイル時のチェックを行う唯一の答えであることです。

他のソリューションでは、「arr」のタイプが変更されると、呼び出しコードがコンパイルされ、実行時に失敗し、実行時のバグが発生します。

私の答えはコードがコンパイルされない原因となるので、間違った型を使用していることを通知するため、コードにバグが含まれる可能性が低くなります。


GlennSlaydenとMehrdadAfshariによって示されるキャストもコンパイル時の証明です。
t3chb0t 2015

1
@ t3chb0tいいえ、そうではありません。はがを実装していることがわかっているため、実行時に機能しますが、それが変更されても、コンパイル時に検出されません。明示的なキャストはコンパイル時の証明にはなりません。代わりに、IEnumerable <Foo>がコンパイル時の証明である暗黙のキャストを使用するため、配列を割り当てたり返したりします。Foo[]IEnumerable<Foo>
トキサントロン2018年

@Toxantronキャストしようとしている型がIEnumerableを実装していない限り、IEnumerable <T>への明示的なキャストはコンパイルされません。「でもそれが変わるなら」と言うとき、あなたが何を意味するのかについての例を提供できますか?
David Klempfner、

もちろんコンパイルします。これが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

これははるかに高い投票になるはずです!コンパイラーの暗黙のキャストを使用することが、最もクリーンで簡単なソリューションです。
トキサントロン2018年

-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実装とほぼ同じです。もちろん、これだけを行う必要があります。これは、努力する価値がある場合です。ほとんどの場合、そうではありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.