Generischen Enumerator aus einem Array abrufen

90

Wie erhält man in C # einen generischen Enumerator aus einem bestimmten Array?

Im folgenden Code MyArraybefindet sich ein Array von MyTypeObjekten. Ich möchte MyIEnumeratorin der gezeigten Weise erhalten, aber es scheint, dass ich einen leeren Enumerator erhalte (obwohl ich das bestätigt habe MyArray.Length > 0).

MyType[] MyArray = ... ;
IEnumerator<MyType> MyIEnumerator = MyArray.GetEnumerator() as IEnumerator<MyType>;
JaysonFix
quelle
Warum möchten Sie aus Neugier den Enumerator bekommen?
David Klempfner
1
@Backwards_Dave In meinem Fall gibt es in einer Umgebung mit einem einzigen Thread eine Liste von Dateien, die jeweils einmal auf asynchrone Weise verarbeitet werden müssen. Ich könnte einen Index verwenden, um zu erhöhen, aber Aufzählungen sind cooler :)
hpaknia

Antworten:

101

Funktioniert mit 2.0+:

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

Funktioniert mit 3.5+ (ausgefallenes LINQy, etwas weniger effizient):

myArray.Cast<MyType>().GetEnumerator()   // returns IEnumerator<MyType>
Mehrdad Afshari
quelle
3
Der LINQy-Code gibt tatsächlich einen Enumerator für das Ergebnis der Cast-Methode zurück und keinen Enumerator für das Array ...
Guffa,
1
Guffa: Da Enumeratoren nur Lesezugriff bieten, ist dies kein großer Unterschied in Bezug auf die Nutzung.
Mehrdad Afshari
8
Wie @Mehrdad impliziert, Am LINQ/Casthat ganz anderes Laufzeitverhalten, da jedes Element des Arrays wird durch einen zusätzlichen Satz übergeben werden MoveNextund Currentenumerator Rundfahrten, und diese Leistung beeinträchtigen könnte , wenn das Array ist riesig. In jedem Fall kann das Problem vollständig und einfach vermieden werden, indem zunächst der richtige Enumerator ermittelt wird (dh mit einer der beiden in meiner Antwort gezeigten Methoden).
Glenn Slayden
7
Das erste ist die bessere Option! Lassen Sie sich von der "überflüssigen LINQy" -Version, die völlig überflüssig ist, nicht täuschen.
Henon
@GlennSlayden Es ist nicht nur langsam für große Arrays, es ist auch langsam für jede Größe von Arrays. Wenn Sie also myArray.Cast<MyType>().GetEnumerator()in Ihrer innersten Schleife verwenden, kann dies Sie selbst bei winzigen Arrays erheblich verlangsamen.
Eugene Beresovsky
54

Sie können selbst entscheiden, ob das Casting hässlich genug ist, um einen fremden Bibliotheksaufruf zu rechtfertigen:

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()
}

Der Vollständigkeit halber sollte man auch beachten, dass das Folgende nicht korrekt ist - und zur Laufzeit abstürzt -, da T[]die nicht generische IEnumerableSchnittstelle für ihre Standardimplementierung (dh nicht explizite) von ausgewählt wird 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>
}

Das Rätsel ist, warum nicht SZGenericArrayEnumerator<T>von SZArrayEnumeratoreiner internen Klasse geerbt wird, die derzeit als "versiegelt" gekennzeichnet ist, da dadurch der (kovariante) generische Enumerator standardmäßig zurückgegeben werden kann.

Glenn Slayden
quelle
Gibt es einen Grund, warum Sie zusätzliche Klammern verwendet haben, ((IEnumerable<int>)arr)aber nur einen Satz von Klammern (IEnumerator<int>)arr?
David Klempfner
1
@Backwards_Dave Ja, das macht den Unterschied zwischen den richtigen Beispielen oben und den falschen Beispielen unten aus. Überprüfen Sie die Operatorrangfolge des C # -unären Operators "cast".
Glenn Slayden
24

Da ich kein Casting mag, ein kleines Update:

your_array.AsEnumerable().GetEnumerator();
Greenoldman
quelle
AsEnumerable () macht auch das Casting, also machst du immer noch das Casting :) Vielleicht könntest du your_array.OfType <T> () .GetEnumerator () verwenden;
Mladen B.
1
Der Unterschied besteht darin, dass es sich um eine implizite Umwandlung zur Kompilierungszeit handelt und nicht um eine explizite Umwandlung zur Laufzeit. Daher treten eher Kompilierungsfehler als Laufzeitfehler auf, wenn der Typ falsch ist.
Hank Schultz
@HankSchultz Wenn der Typ falsch ist, wird er your_array.AsEnumerable()überhaupt nicht kompiliert, da er AsEnumerable()nur für Instanzen von Typen verwendet werden kann, die implementiert werden IEnumerable.
David Klempfner
5

Um es so sauber wie möglich zu machen, lasse ich den Compiler gerne die ganze Arbeit machen. Es gibt keine Abgüsse (also ist es tatsächlich typsicher). Es werden keine Bibliotheken von Drittanbietern (System.Linq) verwendet (kein Laufzeitaufwand).

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

// Und um den Code zu verwenden:

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

Dies nutzt einige Compiler-Magie, die alles sauber hält.

Der andere zu beachtende Punkt ist, dass meine Antwort die einzige Antwort ist, die eine Überprüfung zur Kompilierungszeit durchführt.

Wenn sich bei einer der anderen Lösungen der Typ von "arr" ändert, wird der aufrufende Code kompiliert und schlägt zur Laufzeit fehl, was zu einem Laufzeitfehler führt.

Meine Antwort führt dazu, dass der Code nicht kompiliert wird und ich daher weniger Chancen habe, einen Fehler in meinem Code zu versenden, da dies mir signalisieren würde, dass ich den falschen Typ verwende.

leat
quelle
Das Casting, wie es von GlennSlayden und MehrdadAfshari gezeigt wird, ist ebenfalls kompilierungssicher.
t3chb0t
1
@ t3chb0t nein das sind sie nicht. Die Arbeit zur Laufzeit, weil wir wissen, dass dies Foo[]implementiert wird IEnumerable<Foo>, aber wenn sich dies jemals ändert, wird es zur Kompilierungszeit nicht erkannt. Explizite Casts sind niemals kompilierungssicher. Ordnen Sie stattdessen ein Array zu oder geben Sie es zurück, da IEnumerable <Foo> die implizite Umwandlung verwendet, die für die Kompilierungszeit geeignet ist.
Toxantron
@Toxantron Eine explizite Umwandlung in IEnumerable <T> wird nur kompiliert, wenn der Typ, den Sie umwandeln möchten, IEnumerable implementiert. Wenn Sie sagen "aber wenn sich das jemals ändert", können Sie ein Beispiel dafür geben, was Sie meinen?
David Klempfner
Natürlich kompiliert es. Das ist , was InvalidCastException für ist. Ihr Compiler warnt Sie möglicherweise, dass eine bestimmte Besetzung nicht funktioniert. Versuchen Sie es var foo = (int)new object(). Es kompiliert einwandfrei und stürzt zur Laufzeit ab.
Toxantron
2

YourArray.OfType (). GetEnumerator ();

kann etwas besser abschneiden, da nur der Typ überprüft und nicht gegossen werden muss.

Andrew Dennison
quelle
Sie müssen den Typ explizit angeben, wenn Sie OfType<..type..>()- zumindest in meinem Falldouble[][]
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)
    {

    }
Maxim Q.
quelle
Können Sie bitte erklären, was der obige Code ausführen würde>
Phani
Dies sollte viel höher sein! Die Verwendung der impliziten Besetzung des Compilers ist die sauberste und einfachste Lösung.
Toxantron
-1

Sie können natürlich nur Ihren eigenen generischen Enumerator für Arrays implementieren.

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. */}
    }
}

Dies entspricht mehr oder weniger der .NET-Implementierung von SZGenericArrayEnumerator <T>, wie von Glenn Slayden erwähnt. Sie sollten dies natürlich nur tun, wenn dies die Mühe wert ist. In den meisten Fällen ist dies nicht der Fall.

Corniel Nobel
quelle