obtener un enumerador genérico de una matriz

91

En C #, ¿cómo se obtiene un enumerador genérico de una matriz determinada?

En el código siguiente, MyArrayhay una matriz de MyTypeobjetos. Me gustaría obtener MyIEnumeratorde la manera que se muestra, pero parece que obtengo un enumerador vacío (aunque lo he confirmado MyArray.Length > 0).

MyType[] MyArray = ... ;
IEnumerator<MyType> MyIEnumerator = MyArray.GetEnumerator() as IEnumerator<MyType>;
JaysonFix
fuente
Solo por curiosidad, ¿por qué quiere obtener el enumerador?
David Klempfner
1
@Backwards_Dave en mi caso, en un entorno de un solo subproceso, hay una lista de archivos que cada uno de ellos debe procesarse una vez, de manera asíncrona. Podría utilizar un índice para aumentar, pero enumerables son más frías :)
hpaknia

Respuestas:

102

Funciona en 2.0+:

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

Funciona en 3.5+ (LINQy elegante, un poco menos eficiente):

myArray.Cast<MyType>().GetEnumerator()   // returns IEnumerator<MyType>
Mehrdad Afshari
fuente
3
El código LINQy en realidad devuelve un enumerador para el resultado del método Cast, en lugar de un enumerador para la matriz ...
Guffa
1
Guffa: dado que los enumeradores proporcionan acceso de solo lectura, no hay una gran diferencia en términos de uso.
Mehrdad Afshari
8
Como implica @Mehrdad, Using LINQ/Casttiene un comportamiento en tiempo de ejecución bastante diferente, ya que todos y cada uno de los elementos de la matriz se pasarán a través de un conjunto adicional de recorridos de ida MoveNexty Currentvuelta de enumeradores, y esto podría afectar el rendimiento si la matriz es enorme. En cualquier caso, el problema se evita completa y fácilmente al obtener el enumerador adecuado en primer lugar (es decir, utilizando uno de los dos métodos que se muestran en mi respuesta).
Glenn Slayden
7
¡La primera es la mejor opción! Que nadie se deje engañar por la versión "elegante LINQy" que es completamente superflua.
henon
@GlennSlayden No solo es lento para arreglos grandes, es lento para arreglos de cualquier tamaño. Entonces, si usa myArray.Cast<MyType>().GetEnumerator()en su bucle más interno, puede ralentizarlo significativamente incluso para matrices pequeñas.
Evgeniy Berezovsky
54

Puedes decidir por ti mismo si la transmisión es lo suficientemente fea como para justificar una llamada ajena a la biblioteca:

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

Y para completar, también se debe tener en cuenta que lo siguiente no es correcto, y se bloqueará en tiempo de ejecución, porque T[]elige la interfaz no genérica IEnumerablepara su implementación predeterminada (es decir, no explícita) 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>
}

El misterio es, ¿por qué no SZGenericArrayEnumerator<T>hereda de SZArrayEnumeratoruna clase interna que actualmente está marcada como 'sellada', ya que esto permitiría que el enumerador genérico (covariante) se devuelva de forma predeterminada?

Glenn Slayden
fuente
¿Hay alguna razón por la que utilizó corchetes adicionales ((IEnumerable<int>)arr)pero solo un juego de corchetes adentro (IEnumerator<int>)arr?
David Klempfner
1
@Backwards_Dave Sí, es lo que marca la diferencia entre los ejemplos correctos en la parte superior y el incorrecto en la parte inferior. Compruebe la precedencia del operador del operador de "conversión" unario de C # .
Glenn Slayden
24

Como no me gusta el casting, una pequeña actualización:

your_array.AsEnumerable().GetEnumerator();
greenoldman
fuente
AsEnumerable () también realiza la conversión, por lo que todavía estás haciendo la transmisión :) Quizás puedas usar your_array.OfType <T> () .GetEnumerator ();
Mladen B.
1
La diferencia es que es una conversión implícita realizada en tiempo de compilación en lugar de una conversión explícita realizada en tiempo de ejecución. Por lo tanto, tendrá errores de tiempo de compilación en lugar de errores de tiempo de ejecución si el tipo es incorrecto.
Hank Schultz
@HankSchultz si el tipo es incorrecto your_array.AsEnumerable(), no se compilaría en primer lugar, ya AsEnumerable()que solo se puede usar en instancias de tipos que implementan IEnumerable.
David Klempfner
5

Para hacerlo lo más limpio posible, me gusta dejar que el compilador haga todo el trabajo. No hay yesos (por lo que en realidad es seguro para tipos). No se utilizan bibliotecas de terceros (System.Linq) (sin sobrecarga de tiempo de ejecución).

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

// Y para usar el código:

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

Esto aprovecha la magia del compilador que mantiene todo limpio.

El otro punto a tener en cuenta es que mi respuesta es la única respuesta que realizará la verificación en tiempo de compilación.

Para cualquiera de las otras soluciones, si el tipo de "arr" cambia, el código de llamada se compilará y fallará en tiempo de ejecución, lo que resultará en un error de ejecución.

Mi respuesta hará que el código no se compile y, por lo tanto, tengo menos posibilidades de enviar un error en mi código, ya que me indicaría que estoy usando el tipo incorrecto.

leat
fuente
La conversión como se muestra en GlennSlayden y MehrdadAfshari también es a prueba de tiempo de compilación.
t3chb0t
1
@ t3chb0t no, no lo son. El trabajo en tiempo de ejecución porque sabemos que se Foo[]implementa IEnumerable<Foo>, pero si eso alguna vez cambia, no se detectará en tiempo de compilación. Las conversiones explícitas nunca son a prueba de tiempo de compilación. En su lugar, asigne / devuelva una matriz como IEnumerable <Foo> usa la conversión implícita que es a prueba de tiempo de compilación.
Toxantron
@Toxantron Una conversión explícita a IEnumerable <T> no se compilaría a menos que el tipo que está tratando de transmitir implemente IEnumerable. Cuando dice "pero si eso cambia alguna vez", ¿puede dar un ejemplo de lo que quiere decir?
David Klempfner
Por supuesto que se compila. Para eso es InvalidCastException . Es posible que su compilador le advierta que una determinada conversión no funciona. Prueba var foo = (int)new object(). Se compila bien y se bloquea en tiempo de ejecución.
Toxantron
2

YourArray.OfType (). GetEnumerator ();

Puede que funcione un poco mejor, ya que solo hay que comprobar el tipo y no lanzar.

Andrew Dennison
fuente
OfType<..type..>()double[][]
Debe
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
fuente
¿Puede explicar qué funcionaría el código anterior?
Phani
¡Esto debería ser un voto mucho más alto! Usar la conversión implícita del compilador es la solución más limpia y sencilla.
Toxantron
-1

Lo que puede hacer, por supuesto, es implementar su propio enumerador genérico para matrices.

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

Esto es más o menos igual a la implementación .NET de SZGenericArrayEnumerator <T> como lo menciona Glenn Slayden. Por supuesto, solo debe hacer esto, en los casos en que valga la pena el esfuerzo. En la mayoría de los casos no lo es.

Corniel Nobel
fuente