uzyskać ogólny moduł wyliczający z tablicy

92

W C #, w jaki sposób można uzyskać ogólny moduł wyliczający z danej tablicy?

W poniższym kodzie MyArrayjest tablicą MyTypeobiektów. Chciałbym otrzymać MyIEnumeratorw 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>;
JaysonFix
źródło
Tak z ciekawości, dlaczego chcesz dostać rachmistrza?
David Klempfner
1
@Backwards_Dave w moim przypadku w środowisku jednowątkowym jest lista plików, z których każdy musi zostać przetworzony raz, w sposób asynchroniczny. Mógłbym użyć indeksu do zwiększenia, ale wyliczenia są fajniejsze :)
hpaknia

Odpowiedzi:

104

Działa na 2.0+:

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

Działa na 3.5+ (fantazyjne LINQy, trochę mniej wydajne):

myArray.Cast<MyType>().GetEnumerator()   // returns IEnumerator<MyType>
Mehrdad Afshari
źródło
3
Kod LINQy w rzeczywistości zwraca moduł wyliczający dla wyniku metody Cast, a nie
moduł
1
Guffa: ponieważ moduły wyliczające zapewniają dostęp tylko do odczytu, nie jest to duża różnica w użyciu.
Mehrdad Afshari
8
Jak sugeruje @Mehrdad, Using LINQ/Castma zupełnie inne zachowanie w czasie wykonywania, ponieważ każdy element tablicy zostanie przekazany przez dodatkowy zestaw MoveNexti Currentokrąż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).
Glenn Slayden
7
Pierwsza to lepsza opcja! Nie dajcie się zwieść "fantazyjnej wersji LINQy", która jest całkowicie zbędna.
henon
@GlennSlayden Jest wolny nie tylko w przypadku dużych tablic, ale także w przypadku tablic dowolnego rozmiaru. Więc jeśli używasz myArray.Cast<MyType>().GetEnumerator()w swojej najbardziej wewnętrznej pętli, może to znacznie spowolnić nawet w przypadku małych tablic.
Evgeniy Berezovsky
54

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 -generic IEnumerableinterfejs do swojej domyślnie (czyli bez wyraźnej) realizacja 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>
}

Tajemnica polega na tym, dlaczego nie SZGenericArrayEnumerator<T>dziedziczy z SZArrayEnumerator- 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?

Glenn Slayden
źródło
Czy jest jakiś powód, dla którego użyłeś dodatkowych nawiasów, ((IEnumerable<int>)arr)ale tylko jeden zestaw nawiasów w (IEnumerator<int>)arr?
David Klempfner
1
@Backwards_Dave Tak, to właśnie stanowi różnicę między poprawnymi przykładami na górze a nieprawidłowymi przykładami na dole. Sprawdź pierwszeństwo operatora w C # jednoargumentowego operator „szarego”.
Glenn Slayden
24

Ponieważ nie lubię przesyłać, mała aktualizacja:

your_array.AsEnumerable().GetEnumerator();
Greenoldman
źródło
AsEnumerable () również wykonuje rzutowanie, więc nadal wykonujesz rzutowanie :) Może przydałbyś się your_array.OfType <T> () .GetEnumerator ();
Mladen B.
1
Różnica polega na tym, że jest to niejawne rzutowanie wykonywane w czasie kompilacji, a nie jawne rzutowanie wykonywane w czasie wykonywania. W związku z tym, jeśli typ jest nieprawidłowy, wystąpią błędy czasu kompilacji, a nie błędy czasu wykonania.
Hank Schultz,
@HankSchultz, jeśli typ jest nieprawidłowy 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.
David Klempfner
5

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.

młynówka
źródło
Casting, jak pokazali GlennSlayden i MehrdadAfshari, jest również odporny na kompilację.
t3chb0t
1
@ t3chb0t nie, nie są. Działa w czasie wykonywania, ponieważ wiemy, że Foo[]implementuje IEnumerable<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.
Toxantron
@Toxantron Jawne rzutowanie do IEnumerable <T> nie zostanie skompilowane, chyba że typ, który próbujesz rzutować, implementuje IEnumerable. Kiedy mówisz „ale jeśli to się kiedykolwiek zmieni”, czy możesz podać przykład tego, co masz na myśli?
David Klempfner
Oczywiście, że się kompiluje. Do tego służy InvalidCastException . Twój kompilator może Cię ostrzec, że pewna obsada nie działa. Spróbuj var foo = (int)new object(). Kompiluje się dobrze i ulega awarii w czasie wykonywania.
Toxantron
2

YourArray.OfType (). GetEnumerator ();

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

Andrew Dennison
źródło
Musisz wyraźnie określić typ podczas używania OfType<..type..>()- przynajmniej w moim przypadkudouble[][]
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
źródło
Czy możesz wyjaśnić, co mógłby wykonać powyższy kod>
Phani
To powinno być znacznie wyższe! Użycie niejawnego rzutowania kompilatora jest najczystszym i najłatwiejszym rozwiązaniem.
Toxantron
-1

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.

Corniel Nobel
źródło