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>;

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>

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?


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

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.


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ć.


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)
    {

    }

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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.