2009-08-13 17 views
65

W języku C#, w jaki sposób uzyskać typowy moduł wyliczający z danej tablicy?uzyskać moduł wyliczający z tablicy

W poniższym kodzie jest tablicą obiektów MyType. Chciałbym uzyskać MyIEnumerator w pokazany sposób, , ale wydaje się, że otrzymuję pusty moduł wyliczający (chociaż potwierdziłem, że MyArray.Length > 0).

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

Odpowiedz

79

Prace nad 2.0+:

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

Prace nad nowszej niż 3.5 (fantazyjny LINQy, nieco mniej wydajne):

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

Kod LINQy faktycznie zwraca moduł wyliczający dla wyniku metody Cast, a nie moduł wyliczający dla tablicy ... – Guffa

+1

Guffa: ponieważ moduły wyliczające zapewniają tylko dostęp tylko do odczytu, nie jest to duża różnica pod względem użycia. –

+0

Jak sugeruje @Mehrdad, użycie 'LINQ/Cast' ma zupełnie inne zachowanie środowiska wykonawczego, ponieważ ** każdy element tablicy ** zostanie przekazany przez dodatkowy zestaw cyklicznych wyliczeń' MoveNext' i 'Current', i może to wpłynąć na wydajność, jeśli macierz jest ogromna. W każdym razie problem można całkowicie i łatwo uniknąć przez uzyskanie odpowiedniego modułu wyliczającego (tj. Za pomocą jednej z dwóch metod przedstawionych w mojej odpowiedzi). –

19

Ponieważ nie lubię odlewania, trochę aktualizacja:

your_array.AsEnumerable().GetEnumerator(); 
+0

AsEnumerable() również wykonuje casting, więc nadal wykonujesz obsadę :) Może mógłbyś użyć your_array.OfType () .GetEnumerator(); –

37

Możesz sam zdecydować, czy odlewanie jest dość brzydki uzasadniają obcych połączenia Biblioteka:

int[] arr; 
IEnumerator<int> Get1() 
{ 
    return ((IEnumerable<int>)arr).GetEnumerator(); // <-- 1 non-local call 
    // L_0001: ldarg.0 
    // L_0002: ldfld int32[] foo::arr 
    // L_0007: castclass [mscorlib]System.Collections.Generic.IEnumerable`1<int32> 
    // L_000c: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> [mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator() 
    // L_0011: stloc.0 
} 
IEnumerator<int> Get2() 
{ 
    return arr.AsEnumerable().GetEnumerator(); // <-- 2 non-local calls 
    // L_0001: ldarg.0 
    // L_0002: ldfld int32[] foo::arr 
    // L_0007: call class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0> [System.Core]System.Linq.Enumerable::AsEnumerable<int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>) 
    // L_000c: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> [mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator() 
    // L_0011: stloc.0 
} 

I pod względem kompletności, należy również pamiętać, że poniższe nie jest prawidłowe - i padnie przy starcie - bo T[] wybiera non -generic IEnumerable interfejs dla jego domyślnego (tj niejawna) implementacja GetEnumerator().

IEnumerator<int> NoGet() // error - do not use 
{ 
    return (IEnumerator<int>)arr.GetEnumerator(); 
    // L_0001: ldarg.0 
    // L_0002: ldfld int32[] foo::arr 
    // L_0007: callvirt instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Array::GetEnumerator() 
    // L_000c: castclass [mscorlib]System.Collections.Generic.IEnumerator`1<int32> 
    // L_0011: stloc.0 
} 

Tajemnicą jest, dlaczego nie SZGenericArrayEnumerator<T> dziedziczą z SZArrayEnumerator --an klasy wewnętrznej, która jest obecnie oznaczony jako „zamknięte” - ponieważ pozwoliłoby to (kowariantna) generic wyliczający zostać zwrócony domyślnie?

1

YourArray.OfType(). GetEnumerator();

może działać nieco lepiej, ponieważ musi tylko sprawdzić typ, a nie rzutować.

+0

Musisz wyraźnie określić typ przy użyciu 'OfType <..type..>()' - przynajmniej w moim przypadku 'double [] []' –

3

Aby było tak czyste, jak to możliwe Chciałbym, aby kompilator wykonał całą pracę. Nie ma rzutów (więc jest właściwie bezpieczny). Nie są używane żadne biblioteki stron trzecich (System.Linq) (brak nakładu pracy środowiska wykonawczego).

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

// I użyć kodu:

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

Dzieje przewagę jakiejś magii kompilatora, który utrzymuje wszystko czyste.

Inną kwestią, na którą należy zwrócić uwagę, jest to, że moja odpowiedź jest jedyną odpowiedzią, która przeprowadzi kontrolę podczas kompilacji.

Dla każdego innego rozwiązania, jeśli zmieni się typ "arr", kod wywołujący zostanie skompilowany i zakończy się niepowodzeniem w środowisku wykonawczym, co spowoduje błąd środowiska wykonawczego.

Moja odpowiedź spowoduje, że kod się nie skompiluje, a zatem mam mniejsze szanse na wysłanie błędu w moim kodzie, ponieważ może to sygnalizować, że używam niewłaściwego typu.

+0

Casting, jak pokazuje GlennSlayden i MehrdadAfshari, jest również odporny na kompilację. – t3chb0t

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

    IEnumerable<MyType> enumerable = arr; 

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

    foreach (MyType item in enumerable) 
    { 

    } 
+0

Czy możesz wyjaśnić, co powyższy kod wykonałby> – Phani

0

Co można zrobić, oczywiście, jest po prostu zaimplementować własną rodzajowe wyliczający dla tablic.

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

Jest mniej więcej równa implementacja NET z SZGenericArrayEnumerator <T> jak wspomniano Glenn Slayden. Powinieneś oczywiście tylko to zrobić, to przypadki, w których jest to warte wysiłku. W większości przypadków tak nie jest.

Powiązane problemy