obtenir un énumérateur générique à partir d'un tableau

91

En C #, comment obtenir un énumérateur générique à partir d'un tableau donné?

Dans le code ci-dessous, se MyArraytrouve un tableau d' MyTypeobjets. Je voudrais obtenir MyIEnumeratorde la manière indiquée, mais il semble que j'obtienne un énumérateur vide (bien que je l'ai confirmé MyArray.Length > 0).

MyType[] MyArray = ... ;
IEnumerator<MyType> MyIEnumerator = MyArray.GetEnumerator() as IEnumerator<MyType>;
JaysonFix
la source
Juste par curiosité, pourquoi voulez-vous obtenir le recenseur?
David Klempfner
1
@Backwards_Dave dans mon cas, dans un environnement à thread unique, il existe une liste de fichiers dont chacun doit être traité une fois, de manière asynchrone. Je pourrais utiliser un index pour augmenter, mais les énumérations sont plus cool :)
hpaknia

Réponses:

102

Fonctionne sur 2.0+:

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

Fonctionne sur 3.5+ (fantaisie LINQy, un peu moins efficace):

myArray.Cast<MyType>().GetEnumerator()   // returns IEnumerator<MyType>
Mehrdad Afshari
la source
3
Le code LINQy renvoie en fait un énumérateur pour le résultat de la méthode Cast, plutôt qu'un énumérateur pour le tableau ...
Guffa
1
Guffa: puisque les énumérateurs fournissent un accès en lecture seule, ce n'est pas une grande différence en termes d'utilisation.
Mehrdad Afshari
8
Comme @Mehrdad implique, l' aide LINQ/Casta un comportement d'exécution tout à fait différent, puisque chaque élément du tableau sera passé à travers un jeu supplémentaire de MoveNextet Currentallers-retours-recenseur, et cela pourrait affecter les performances si le tableau est énorme. Dans tous les cas, le problème est complètement et facilement évité en obtenant le bon énumérateur en premier lieu (c'est-à-dire en utilisant l'une des deux méthodes indiquées dans ma réponse).
Glenn Slayden
7
La première est la meilleure option! Personne ne se laisse tromper par la version "fantaisie LINQy" qui est complètement superflue.
henon
@GlennSlayden Ce n'est pas seulement lent pour les tableaux énormes, c'est lent pour toutes les tailles de tableaux. Donc, si vous utilisez myArray.Cast<MyType>().GetEnumerator()dans votre boucle la plus interne, cela peut vous ralentir considérablement, même pour de minuscules tableaux.
Evgeniy Berezovsky
54

Vous pouvez décider vous-même si la diffusion est assez moche pour justifier un appel de bibliothèque superflu:

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

Et pour être complet, il faut également noter que ce qui suit n'est pas correct - et plantera à l'exécution - car T[]choisit l' interface non générique IEnumerablepour son implémentation par défaut (c'est-à-dire non explicite) de 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>
}

Le mystère est, pourquoi SZGenericArrayEnumerator<T>n'hérite pas de SZArrayEnumerator--une classe interne qui est actuellement marquée «scellée» - puisque cela permettrait à l'énumérateur générique (covariant) d'être retourné par défaut?

Glenn Slayden
la source
Y a-t-il une raison pour laquelle vous avez utilisé des parenthèses supplémentaires dans ((IEnumerable<int>)arr)un seul jeu de parenthèses (IEnumerator<int>)arr?
David Klempfner
1
@Backwards_Dave Oui, c'est ce qui fait la différence entre les bons exemples en haut et les mauvais en bas. Vérifiez la priorité des opérateurs de l' opérateur "cast" unaire C # .
Glenn Slayden
24

Puisque je n'aime pas le casting, une petite mise à jour:

your_array.AsEnumerable().GetEnumerator();
homme vert
la source
AsEnumerable () fait également le casting, donc vous faites toujours le cast :) Peut-être que vous pourriez utiliser your_array.OfType <T> () .GetEnumerator ();
Mladen B.
1
La différence est qu'il s'agit d'un cast implicite effectué au moment de la compilation plutôt que d'un cast explicite effectué au moment de l'exécution. Par conséquent, vous aurez des erreurs de compilation plutôt que des erreurs d'exécution si le type est incorrect.
Hank Schultz
@HankSchultz si le type est incorrect your_array.AsEnumerable(), ne compilerait pas en premier lieu car AsEnumerable()il ne peut être utilisé que sur les instances de types qui implémentent IEnumerable.
David Klempfner
5

Pour le rendre aussi propre que possible, j'aime laisser le compilateur faire tout le travail. Il n'y a pas de moulages (donc c'est en fait sûr de type). Aucune bibliothèque tierce (System.Linq) n'est utilisée (aucune surcharge d'exécution).

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

// Et pour utiliser le code:

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

Cela tire parti de la magie du compilateur qui garde tout propre.

L'autre point à noter est que ma réponse est la seule réponse qui fera une vérification au moment de la compilation.

Pour toutes les autres solutions, si le type de "arr" change, alors le code d'appel sera compilé et échouera à l'exécution, ce qui provoquera un bogue d'exécution.

Ma réponse fera que le code ne se compilera pas et donc j'ai moins de chance d'envoyer un bogue dans mon code, car cela me signifierait que j'utilise le mauvais type.

leat
la source
Le casting comme le montrent GlennSlayden et MehrdadAfshari est également une preuve à la compilation.
t3chb0t
1
@ t3chb0t non, ils ne le sont pas. Le travail au moment de l'exécution car nous savons que cela Foo[]implémente IEnumerable<Foo>, mais si cela change jamais, il ne sera pas détecté au moment de la compilation. Les transtypages explicites ne sont jamais à l'épreuve de la compilation. Au lieu de cela, attribuer / renvoyer le tableau en tant que IEnumerable <Foo> utilise le cast implicite qui est une preuve à la compilation.
Toxantron
@Toxantron Un cast explicite en IEnumerable <T> ne serait pas compilé à moins que le type que vous essayez de convertir implémente IEnumerable. Lorsque vous dites «mais si jamais cela change», pouvez-vous donner un exemple de ce que vous voulez dire?
David Klempfner
Bien sûr, il compile. C'est à cela que sert InvalidCastException . Votre compilateur peut vous avertir qu'une certaine distribution ne fonctionne pas. Essayez var foo = (int)new object(). Il se compile très bien et plante au moment de l'exécution.
Toxantron
2

YourArray.OfType (). GetEnumerator ();

peut fonctionner un peu mieux, car il suffit de vérifier le type et non de lancer.

Andrew Dennison
la source
Vous devez spécifier explicitement le type lors de l'utilisation OfType<..type..>()- au moins dans mon cas dedouble[][]
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
la source
Pouvez-vous s'il vous plaît expliquer ce que le code ci-dessus effectuerait>
Phani
Cela devrait être un vote beaucoup plus élevé! L'utilisation du cast implicite du compilateur est la solution la plus propre et la plus simple.
Toxantron
-1

Ce que vous pouvez faire, bien sûr, c'est simplement implémenter votre propre énumérateur générique pour les tableaux.

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

C'est plus ou moins égal à l'implémentation .NET de SZGenericArrayEnumerator <T> comme mentionné par Glenn Slayden. Vous ne devriez bien sûr le faire que dans les cas où cela en vaut la peine. Dans la plupart des cas, ce n'est pas le cas.

Corniel Nobel
la source