obter enumerador genérico de uma matriz

91

Em C #, como se obtém um enumerador genérico de uma determinada matriz?

No código abaixo, MyArrayé uma matriz de MyTypeobjetos. Gostaria de obter MyIEnumeratorda forma indicada, mas parece que obtenho um enumerador vazio (embora o tenha confirmado MyArray.Length > 0).

MyType[] MyArray = ... ;
IEnumerator<MyType> MyIEnumerator = MyArray.GetEnumerator() as IEnumerator<MyType>;
JaysonFix
fonte
Só por curiosidade, por que você quer chamar o enumerador?
David Klempfner
1
@Backwards_Dave no meu caso, em um ambiente de thread único, há uma lista de arquivos em que cada um deles deve ser processado uma vez, de maneira Async. Eu poderia usar um índice para aumentar, mas os enumeráveis ​​são mais
legais

Respostas:

102

Funciona em 2.0+:

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

Funciona em 3.5+ (LINQy sofisticado, um pouco menos eficiente):

myArray.Cast<MyType>().GetEnumerator()   // returns IEnumerator<MyType>
Mehrdad Afshari
fonte
3
O código LINQy realmente retorna um enumerador para o resultado do método Cast, em vez de um enumerador para a matriz ...
Guffa
1
Guffa: já que os enumeradores fornecem acesso somente leitura, não é uma grande diferença em termos de uso.
Mehrdad Afshari
8
Como @Mehrdad implica, Usando LINQ/Casttem um comportamento de tempo de execução muito diferente, uma vez que cada elemento da matriz será passado através de um conjunto extra de MoveNexte Currentenumerador round-trips, e isso pode afetar o desempenho se a matriz é enorme. Em qualquer caso, o problema é completamente e facilmente evitado obtendo-se o enumerador adequado em primeiro lugar (ou seja, usando um dos dois métodos mostrados em minha resposta).
Glenn Slayden
7
A primeira é a melhor opção! Ninguém se deixe enganar pela versão "extravagante do LINQy" que é completamente supérflua.
henon
@GlennSlayden Não é apenas lento para arrays enormes, é lento para arrays de qualquer tamanho. Portanto, se você usar myArray.Cast<MyType>().GetEnumerator()em seu loop mais interno, poderá diminuir sua velocidade significativamente, mesmo para matrizes minúsculas.
Evgeniy Berezovsky
54

Você pode decidir por si mesmo se o elenco é feio o suficiente para justificar uma chamada externa à 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()
}

E para completar, deve-se observar também que o seguinte não está correto - e irá travar em tempo de execução - porque T[]escolhe a interface não genérica IEnumerablepara sua implementação padrão (ou seja, não 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>
}

O mistério é: por que não SZGenericArrayEnumerator<T>herda de SZArrayEnumerator- uma classe interna que atualmente está marcada como 'selada' - já que isso permitiria que o enumerador genérico (covariante) fosse retornado por padrão?

Glenn Slayden
fonte
Existe uma razão pela qual você usou colchetes extras, ((IEnumerable<int>)arr)mas apenas um conjunto de colchetes (IEnumerator<int>)arr?
David Klempfner
1
@Backwards_Dave Sim, é o que faz a diferença entre os exemplos corretos na parte superior e o incorreto na parte inferior. Verifique a precedência do operador do operador de "conversão" unário C # .
Glenn Slayden
24

Já que não gosto de lançar, uma pequena atualização:

your_array.AsEnumerable().GetEnumerator();
Greenoldman
fonte
AsEnumerable () também faz a conversão, então você ainda está fazendo a conversão :) Talvez você possa usar your_array.OfType <T> () .GetEnumerator ();
Mladen B.
1
A diferença é que é uma conversão implícita feita em tempo de compilação, em vez de uma conversão explícita feita em tempo de execução. Portanto, você terá erros de tempo de compilação em vez de erros de tempo de execução se o tipo estiver errado.
Hank Schultz,
@HankSchultz se o tipo estiver errado então your_array.AsEnumerable()não seria compilado em primeiro lugar, pois AsEnumerable()só pode ser usado em instâncias de tipos que implementam IEnumerable.
David Klempfner
5

Para deixar o mais limpo possível, gosto de deixar o compilador fazer todo o trabalho. Não há casts (portanto, é realmente seguro para tipos). Nenhuma biblioteca de terceiros (System.Linq) é usada (sem sobrecarga de tempo de execução).

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

// E para usar o código:

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

Isso tira vantagem de alguma mágica do compilador que mantém tudo limpo.

O outro ponto a ser observado é que minha resposta é a única que fará a verificação em tempo de compilação.

Para qualquer uma das outras soluções, se o tipo de "arr" mudar, o código de chamada será compilado e falhará no tempo de execução, resultando em um bug de tempo de execução.

Minha resposta fará com que o código não compile e, portanto, tenho menos chance de enviar um bug no meu código, pois isso me indicaria que estou usando o tipo errado.

leat
fonte
Casting conforme mostrado por GlennSlayden e MehrdadAfshari também é prova em tempo de compilação.
t3chb0t
1
@ t3chb0t não, não são. O trabalho em tempo de execução porque sabemos que Foo[]implementa IEnumerable<Foo>, mas se isso mudar, não será detectado em tempo de compilação. Casts explícitos nunca são à prova de tempo de compilação. Em vez disso, atribua / retorne a matriz como IEnumerable <Foo> usa a conversão implícita que é à prova de tempo de compilação.
Toxantron
@Toxantron Uma conversão explícita para IEnumerable <T> não seria compilada, a menos que o tipo que você está tentando converter implemente IEnumerable. Quando diz "mas se isso mudar algum dia", pode dar um exemplo do que quer dizer?
David Klempfner
Claro que ele compila. É para isso que serve InvalidCastException . Seu compilador pode avisá-lo de que um determinado elenco não funciona. Experimente var foo = (int)new object(). Ele compila perfeitamente e falha no tempo de execução.
Toxantron de
2

YourArray.OfType (). GetEnumerator ();

pode ter um desempenho um pouco melhor, já que só tem que verificar o tipo, e não lançar.

Andrew Dennison
fonte
Você deve especificar explicitamente o tipo ao usar OfType<..type..>()- pelo menos no meu caso 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
fonte
Você pode explicar o que o código acima funcionaria>
Phani
Este deve ser um voto muito mais alto! Usar a conversão implícita do compilador é a solução mais limpa e fácil.
Toxantron
-1

O que você pode fazer, é claro, é apenas implementar seu próprio enumerador genérico para arrays.

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

Isso é mais ou menos igual à implementação do .NET de SZGenericArrayEnumerator <T> conforme mencionado por Glenn Slayden. É claro que você só deve fazer isso nos casos em que vale a pena o esforço. Na maioria dos casos, não é.

Corniel Nobel
fonte