Pergunta

Esta é em C #, eu tenho uma classe que estou usando de DLL alguma outra pessoa. Ele não implementa IEnumerable, mas tem 2 métodos que passam volta um IEnumerator. Existe uma maneira que eu posso usar um loop foreach sobre estes. A classe que estou usando é selado.

Foi útil?

Solução

foreach não requerem IEnumerable, ao contrário da crença popular. Tudo que requer é um GetEnumerator método que retorna qualquer objeto que tem o MoveNext método eo Current get-propriedade com as assinaturas apropriadas.

/ EDIT: No seu caso, no entanto, você está sem sorte. Você pode trivialmente envolver seu objeto, no entanto, para torná-lo enumeráveis:

class EnumerableWrapper {
    private readonly TheObjectType obj;

    public EnumerableWrapper(TheObjectType obj) {
        this.obj = obj;
    }

    public IEnumerator<YourType> GetEnumerator() {
        return obj.TheMethodReturningTheIEnumerator();
    }
}

// Called like this:

foreach (var xyz in new EnumerableWrapper(yourObj))
    …;

/ EDIT: O método a seguir, proposta por várias pessoas, não trabalho se o método retorna um IEnumerator:

foreach (var yz in yourObj.MethodA())
    …;

Outras dicas

Re: Se foreach não exige um contrato de interface explícita, ele encontra GetEnumerator usando reflexão?

(eu não posso comentário desde que eu não tenho uma reputação bastante elevada.)

Se você está insinuando tempo de execução reflexão então não. Ele faz tudo compiletime, outro fato menos conhecido é que ele também verificar para ver se o objeto retornado que pode Implementar IEnumerator é descartável.

Para ver isso em ação considerar este (executável) snippet.


using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication3
{
    class FakeIterator
    {
        int _count;

        public FakeIterator(int count)
        {
            _count = count;
        }
        public string Current { get { return "Hello World!"; } }
        public bool MoveNext()
        {
            if(_count-- > 0)
                return true;
            return false;
        }
    }

    class FakeCollection
    {
        public FakeIterator GetEnumerator() { return new FakeIterator(3); }
    }

    class Program
    {
        static void Main(string[] args)
        {
            foreach (string value in new FakeCollection())
                Console.WriteLine(value);
        }
    }
}

De acordo com a MSDN :

foreach (type identifier in expression) statement

onde a expressão é:

coleção de objetos ou expressão de matriz. O tipo de elemento de recolha deve ser conversível para o identificador tipo. Não use uma expressão que avalia como nulo. Avalia a um tipo que implementa IEnumerable ou um tipo que declara um método GetEnumerator. Neste último caso, GetEnumerator deve ou retornar um tipo que implementos IEnumerator ou declara tudo os métodos definidos no IEnumerator.

Resposta curta:

Você precisa de uma classe com um método chamado GetEnumerator , que retorna o IEnumerator você já tem. Conseguir isso com um invólucro simples:

class ForeachWrapper
{
  private IEnumerator _enumerator;

  public ForeachWrapper(Func<IEnumerator> enumerator)
  {
    _enumerator = enumerator;
  }

  public IEnumerator GetEnumerator()
  {
    return _enumerator();
  }
}

Uso:

foreach (var element in new ForeachWrapper(x => myClass.MyEnumerator()))
{
  ...
}

Do Linguagem C # Especificação :

O tempo de compilação processamento de um declaração foreach primeiro determina o tipo de coleção, tipo recenseador e elemento tipo da expressão. este determinação prossegue como se segue:

  • Se o tipo X de expressão é um tipo de matriz, em seguida, existe uma implícita referência a conversão de X para o System.Collections.IEnumerable Interface (desde System.Array implementa essa interface). o tipo de coleção é a System.Collections.IEnumerable interface, do tipo enumerador é a System.Collections.IEnumerator interface e o tipo do elemento é a elemento do tipo do tipo de matriz X.

  • Caso contrário, determinar se o tipo de X tem um apropriado GetEnumerator método:

    • Executar pesquisa membro do tipo X com identificador GetEnumerator e nenhuma argumentos de tipo. Se a pesquisa membro não produz um jogo, ou ele produz uma ambiguidade, ou produz um jogo que não é um grupo de método, verificar se há uma interface enumeráveis ??como Descrito abaixo. É recomendado que um aviso é emitido se membro lookup produz nada além de um grupo método ou nenhuma correspondência.

    • Executar resolução de sobrecarga usando o método de grupo resultante e um lista de argumentos vazia. Se a sobrecarga resultados de resolução em nenhum caso métodos, resulta numa ambiguidade, ou resulta em um único melhor método, mas esse método é estático ou não público, verificação para um enumeráveis interface como descrito abaixo. Isto é recomendou que um aviso será emitido Se a resolução de sobrecarga produz qualquer coisa, exceto um público inequívoca método de instância ou não aplicável métodos.

    • Se o tipo de retorno E do método GetEnumerator não é uma classe, struct ou tipo de interface, um erro é produzidos e não há outros passos são tomadas.

    • lookup Membro é realizada em E com o identificador atual e nenhuma argumentos de tipo. Se a pesquisa membro não produz nenhum jogo, o resultado é um erro, ou o resultado é nada exceto a propriedade de instância público que permite leitura, um erro é produzido e há outras medidas são tomadas.

    • lookup Membro é realizada em E com o identificador MoveNext e nenhuma argumentos de tipo. Se a pesquisa membro não produz nenhum jogo, o resultado é um erro, ou o resultado é nada excepto um grupo método, um erro é produzidos e não há outros passos são tomadas.

    • Resolução de sobrecarga é executada no grupo método com um vazio lista de argumentos. Se a resolução de sobrecarga resulta em nenhum dos métodos aplicáveis, resulta numa ambiguidade, ou resulta em um único melhor método, mas esse método é estático ou não pública, ou seu tipo de retorno não é bool, um erro é produzidos e não há outros passos são tomadas.

    • O tipo de recolha é X, o tipo enumerador é E, e o elemento tipo é o tipo do actual propriedade.

  • Caso contrário, verifique se há uma interface enumeráveis:

    • Se há exatamente um tipo T tal que há um implícito a conversão de X para a interface System.Collections.Generic.IEnumerable , em seguida, o tipo de coleção é este interface, do tipo enumerador é a interface System.Collections.Generic.IEnumerator , e o tipo de elemento é T.

    • Caso contrário, se houver mais de um tal tipo T, então um erro é produzidos e não há outros passos são tomadas.

    • Caso contrário, se há is uma conversão implícita de X para o System.Collections.IEnumerable interface, em seguida, o tipo de recolha é esta interface, o tipo de enumerador é a interface System.Collections.IEnumerator, e o tipo de elemento é objeto.

    • Caso contrário, um erro é produzido e há outras medidas são tomadas.

Não estritamente. Enquanto a classe tem o necessário GetEnumerator, MoveNext, Reset, e os membros atuais, ele vai trabalhar com foreach

Não, você não e você não precisa mesmo de um método GetEnumerator, por exemplo:.

class Counter
{
    public IEnumerable<int> Count(int max)
    {
        int i = 0;
        while (i <= max)
        {
            yield return i;
            i++;
        }
        yield break;
    }
}

que é chamado desta maneira:

Counter cnt = new Counter();

foreach (var i in cnt.Count(6))
{
    Console.WriteLine(i);
}

Você pode sempre envolvê-la, e como um aparte para ser "foreachable" você só precisa ter um método chamado "GetEnumerator" com a assinatura apropriada.


class EnumerableAdapter
{
  ExternalSillyClass _target;

  public EnumerableAdapter(ExternalSillyClass target)
  {
    _target = target;
  }

  public IEnumerable GetEnumerator(){ return _target.SomeMethodThatGivesAnEnumerator(); }

}

Dada a classe X com os métodos A e B que tanto retorno IEnumerable, você poderia usar um foreach na classe como este:

foreach (object y in X.A())
{
    //...
}

// or

foreach (object y in X.B())
{
   //...
}

Presumivelmente o significado para as enumerables retornados por A e B são bem definido.

@ Brian: Não tenho certeza você tentar varrer o valor de retorno de chamada de método ou a própria classe, Se o que você quer é a classe, em seguida, por torná-lo uma matriz que você pode usar com foreach.

Para uma classe para ser utilizável com foeach tudo o que precisa fazer é ter um método público que retorna e IEnumerator chamado GetEnumerator (), é isso:

Tome a seguinte classe, que não implementa IEnumerable ou IEnumerator:

public class Foo
{
    private int[] _someInts = { 1, 2, 3, 4, 5, 6 };
    public IEnumerator GetEnumerator()
    {
        foreach (var item in _someInts)
        {
            yield return item;
        }
    }
}

método alternativamente o GetEnumerator () pode ser escrita:

    public IEnumerator GetEnumerator()
    {
        return _someInts.GetEnumerator();
    }

Quando usado em um foreach (Note que a nenhum envoltório é usado, apenas uma instância da classe):

    foreach (int item in new Foo())
    {
        Console.Write("{0,2}",item);
    }

impressões:

1 2 3 4 5 6

O tipo requer apenas para ter um público / non-static / não-genérico método / parâmetros chamado GetEnumerator que deve retornar algo que tem um método MoveNext pública e uma propriedade Current público. Como eu recordo o Sr. Eric Lippert em algum lugar, este foi concebido de modo a acomodar era pré genérico tanto para a segurança de tipos e boxe problemas de desempenho relacionados em caso de tipos de valor.

Para instância isso funciona:

class Test
{
    public SomethingEnumerator GetEnumerator()
    {

    }
}

class SomethingEnumerator
{
    public Something Current //could return anything
    {
        get { }
    }

    public bool MoveNext()
    {

    }
}

//now you can call
foreach (Something thing in new Test()) //type safe
{

}

Este é então traduzido pelo compilador para:

var enumerator = new Test().GetEnumerator();
try {
   Something element; //pre C# 5
   while (enumerator.MoveNext()) {
      Something element; //post C# 5
      element = (Something)enumerator.Current; //the cast!
      statement;
   }
}
finally {
   IDisposable disposable = enumerator as System.IDisposable;
   if (disposable != null) disposable.Dispose();
}

De 8.8.4 secção da especificação .


vale alguma coisa de nota é a precedência recenseador envolvido - ele vai como se você tem um método public GetEnumerator, em seguida, que é a opção padrão de foreach independentemente de quem está implementando. Por exemplo:

class Test : IEnumerable<int>
{
    public SomethingEnumerator GetEnumerator()
    {
        //this one is called
    }

    IEnumerator<int> IEnumerable<int>.GetEnumerator()
    {

    }
}

( Se você não tem uma implementação pública (implementação ou seja, apenas explícito), então precedência vai como IEnumerator<T>> IEnumerator. )

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top