ottenere un enumeratore generico da un array


91

In C #, come si ottiene un enumeratore generico da un dato array?

Nel codice seguente, MyArrayc'è un array di MyTypeoggetti. Vorrei ottenere MyIEnumeratornella moda mostrata, ma sembra che ottengo un enumeratore vuoto (anche se l'ho confermato MyArray.Length > 0).

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

Solo per curiosità, perché vuoi ottenere l'enumeratore?
David Klempfner

1
@Backwards_Dave nel mio caso in un ambiente a thread singolo c'è un elenco di file che ciascuno di essi deve essere elaborato una volta, in modo asincrono. Potrei usare un indice per aumentare, ma gli enumerabili sono più interessanti :)
hpaknia

Risposte:


102

Funziona su 2.0+:

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

Funziona su 3.5+ (LINQy fantasia, un po 'meno efficiente):

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

3
Il codice LINQy restituisce effettivamente un enumeratore per il risultato del metodo Cast, piuttosto che un enumeratore per l'array ...
Guffa

1
Guffa: poiché gli enumeratori forniscono solo l'accesso in sola lettura, non è una grande differenza in termini di utilizzo.
Mehrdad Afshari

8
Come suggerisce @Mehrdad, Usare LINQ/Castha un comportamento runtime molto diverso, dal momento che ogni elemento della matrice verrà fatto passare attraverso un ulteriore set di MoveNexte Currentenumeratore di andata e ritorno, e questo potrebbe influire sulle prestazioni se la matrice è enorme. In ogni caso, il problema è completamente e facilmente evitato ottenendo in primo luogo l'enumeratore appropriato (cioè utilizzando uno dei due metodi mostrati nella mia risposta).
Glenn Slayden

7
La prima è l'opzione migliore! Nessuno si lasci ingannare dalla versione "fantasia LINQy" che è del tutto superflua.
henon

@GlennSlayden Non è solo lento per array enormi, è lento per array di qualsiasi dimensione. Quindi, se lo usi myArray.Cast<MyType>().GetEnumerator()nel tuo loop più interno, potrebbe rallentarti in modo significativo anche per piccoli array.
Evgeniy Berezovsky

54

Puoi decidere da solo se il casting è abbastanza brutto da giustificare una chiamata di libreria estranea:

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

E per completezza, si dovrebbe anche notare che quanto segue non è corretto - e andrà in crash in fase di esecuzione - perché T[]sceglie l' interfaccia non generica IEnumerableper la sua implementazione predefinita (cioè non esplicita) di 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>
}

Il mistero è, perché non SZGenericArrayEnumerator<T>eredita da SZArrayEnumerator- una classe interna che è attualmente contrassegnata come "sealed" - poiché ciò consentirebbe di restituire l'enumeratore generico (covariante) per impostazione predefinita?


C'è un motivo per cui hai usato parentesi extra in ((IEnumerable<int>)arr)ma solo un set di parentesi (IEnumerator<int>)arr?
David Klempfner,

1
@Backwards_Dave Sì, è ciò che fa la differenza tra gli esempi corretti in alto e quello errato in basso. Controllare la precedenza degli operatori del C # operatore unario "cast".
Glenn Slayden

24

Dato che non mi piace il casting, un piccolo aggiornamento:

your_array.AsEnumerable().GetEnumerator();

AsEnumerable () fa anche il casting, quindi stai ancora facendo il cast :) Forse potresti usare your_array.OfType <T> () .GetEnumerator ();
Mladen B.

1
La differenza è che si tratta di un cast implicito eseguito in fase di compilazione piuttosto che di un cast esplicito eseguito in fase di runtime. Pertanto, avrai errori in fase di compilazione anziché errori di runtime se il tipo è sbagliato.
Hank Schultz

@HankSchultz se il tipo è sbagliato your_array.AsEnumerable(), non verrà compilato in primo luogo poiché AsEnumerable()può essere utilizzato solo su istanze di tipi che implementano IEnumerable.
David Klempfner,

5

Per renderlo il più pulito possibile, mi piace lasciare che il compilatore faccia tutto il lavoro. Non ci sono cast (quindi è effettivamente indipendente dai tipi). Non vengono utilizzate librerie di terze parti (System.Linq) (nessun overhead di runtime).

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

// E per usare il codice:

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

Questo sfrutta un po 'di magia del compilatore che mantiene tutto pulito.

L'altro punto da notare è che la mia risposta è l'unica che eseguirà il controllo in fase di compilazione.

Per una qualsiasi delle altre soluzioni, se il tipo di "arr" cambia, il codice chiamante verrà compilato e avrà esito negativo in fase di esecuzione, determinando un bug di runtime.

La mia risposta farà sì che il codice non venga compilato e quindi ho meno possibilità di inviare un bug nel mio codice, poiché mi segnalerebbe che sto usando il tipo sbagliato.


Anche il casting come mostrato da GlennSlayden e MehrdadAfshari è una prova in fase di compilazione.
t3chb0t

1
@ t3chb0t no non lo sono. Il lavoro in fase di esecuzione perché sappiamo che Foo[]implementa IEnumerable<Foo>, ma se questo cambia mai non verrà rilevato in fase di compilazione. I cast espliciti non sono mai una prova in fase di compilazione. Invece, assegnare / restituire array come IEnumerable <Foo> usa il cast implicito che è a prova di compilazione.
Toxantron

@Toxantron Un cast esplicito a IEnumerable <T> non viene compilato a meno che il tipo che stai tentando di eseguire non implementi IEnumerable. Quando dici "ma se questo dovesse mai cambiare", puoi fornire un esempio di cosa intendi?
David Klempfner

Ovviamente si compila. Ecco a cosa serve InvalidCastException . Il tuo compilatore potrebbe avvisarti che un certo cast non funziona. Prova var foo = (int)new object(). Si compila bene e si blocca in fase di esecuzione.
Toxantron

2

YourArray.OfType (). GetEnumerator ();

può funzionare un po 'meglio, poiché deve solo controllare il tipo e non lanciare.


È necessario specificare esplicitamente il tipo quando si utilizza OfType<..type..>()- almeno nel mio caso didouble[][]
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)
    {

    }

Puoi per favore spiegare cosa si comporterebbe il codice sopra?
Phani

Questo dovrebbe essere un voto molto più alto! L'uso del cast implicito del compilatore è la soluzione più pulita e semplice.
Toxantron

-1

Quello che puoi fare, ovviamente, è semplicemente implementare il tuo enumeratore generico per gli array.

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

Questo è più o meno uguale all'implementazione .NET di SZGenericArrayEnumerator <T> come menzionato da Glenn Slayden. Ovviamente dovresti farlo solo nei casi in cui ne vale la pena. Nella maggior parte dei casi non lo è.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.