W C #, w jaki sposób można uzyskać ogólny moduł wyliczający z danej tablicy?
W poniższym kodzie MyArray
jest tablicą MyType
obiektów. Chciałbym otrzymać MyIEnumerator
w pokazany sposób, ale wydaje mi się, że otrzymuję pusty licznik (chociaż to potwierdziłem MyArray.Length > 0
).
MyType[] MyArray = ... ;
IEnumerator<MyType> MyIEnumerator = MyArray.GetEnumerator() as IEnumerator<MyType>;
c#
arrays
generics
ienumerator
JaysonFix
źródło
źródło
Odpowiedzi:
Działa na 2.0+:
Działa na 3.5+ (fantazyjne LINQy, trochę mniej wydajne):
myArray.Cast<MyType>().GetEnumerator() // returns IEnumerator<MyType>
źródło
LINQ/Cast
ma zupełnie inne zachowanie w czasie wykonywania, ponieważ każdy element tablicy zostanie przekazany przez dodatkowy zestawMoveNext
iCurrent
okrążenia modułu wyliczającego, co może wpłynąć na wydajność, jeśli tablica jest ogromna. W każdym razie problemu można całkowicie i łatwo uniknąć, uzyskując w pierwszej kolejności odpowiedni moduł wyliczający (tj. Jedną z dwóch metod przedstawionych w mojej odpowiedzi).myArray.Cast<MyType>().GetEnumerator()
w swojej najbardziej wewnętrznej pętli, może to znacznie spowolnić nawet w przypadku małych tablic.Możesz sam zdecydować, czy casting jest wystarczająco brzydki, aby uzasadnić obce połączenie z biblioteką:
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() }
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 -genericIEnumerable
interfejs do swojej domyślnie (czyli bez wyraźnej) realizacjaGetEnumerator()
.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> }
Tajemnica polega na tym, dlaczego nie
SZGenericArrayEnumerator<T>
dziedziczy zSZArrayEnumerator
- klasy wewnętrznej, która jest obecnie oznaczona jako „zapieczętowana” - skoro umożliwiłoby to domyślne zwrócenie (kowariantnego) generycznego modułu wyliczającego?źródło
((IEnumerable<int>)arr)
ale tylko jeden zestaw nawiasów w(IEnumerator<int>)arr
?Ponieważ nie lubię przesyłać, mała aktualizacja:
źródło
your_array.AsEnumerable()
, nie zostanie skompilowany w pierwszej kolejności, ponieważAsEnumerable()
może być używany tylko w wystąpieniach typów, które implementująIEnumerable
.Aby uczynić go tak czystym, jak to tylko możliwe, wolę pozwolić kompilatorowi wykonać całą pracę. Nie ma rzutów (więc w rzeczywistości jest bezpieczny dla typów). Nie są używane żadne biblioteki innych firm (System.Linq) (brak narzutu czasu wykonywania).
public static IEnumerable<T> GetEnumerable<T>(this T[] arr) { return arr; }
// I żeby użyć kodu:
String[] arr = new String[0]; arr.GetEnumerable().GetEnumerator()
Wykorzystuje to pewną magię kompilatora, która utrzymuje wszystko w czystości.
Inną kwestią, na którą należy zwrócić uwagę, jest to, że moja odpowiedź jest jedyną odpowiedzią, która będzie sprawdzać podczas kompilacji.
W przypadku innych rozwiązań, jeśli typ „arr” ulegnie zmianie, wywołanie kodu zostanie skompilowane i zakończy się niepowodzeniem w czasie wykonywania, co spowoduje błąd w czasie wykonywania.
Moja odpowiedź spowoduje, że kod się nie skompiluje i dlatego mam mniejsze szanse na wysłanie błędu w moim kodzie, ponieważ sygnalizowałoby to, że używam niewłaściwego typu.
źródło
Foo[]
implementujeIEnumerable<Foo>
, ale jeśli to kiedykolwiek się zmieni, nie zostanie to wykryte w czasie kompilacji. Jawne rzuty nigdy nie są dowodem w czasie kompilacji. Zamiast tego przypisywanie / zwracanie tablicy, ponieważ IEnumerable <Foo> używa niejawnego rzutowania, które jest dowodem w czasie kompilacji.var foo = (int)new object()
. Kompiluje się dobrze i ulega awarii w czasie wykonywania.YourArray.OfType (). GetEnumerator ();
może działać trochę lepiej, ponieważ musi tylko sprawdzić typ, a nie rzutować.
źródło
OfType<..type..>()
- przynajmniej w moim przypadkudouble[][]
MyType[] arr = { new MyType(), new MyType(), new MyType() }; IEnumerable<MyType> enumerable = arr; IEnumerator<MyType> en = enumerable.GetEnumerator(); foreach (MyType item in enumerable) { }
źródło
Możesz oczywiście po prostu zaimplementować własny ogólny moduł 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 to mniej więcej równe implementacji SZGenericArrayEnumerator <T> .NET, jak wspomniał Glenn Slayden. Powinieneś to oczywiście robić tylko w przypadkach, w których jest to warte wysiłku. W większości przypadków tak nie jest.
źródło