Pregunta

Esto está en C#, tengo una clase que estoy usando de la DLL de otra persona.No implementa IEnumerable pero tiene 2 métodos que devuelven un IEnumerator.¿Hay alguna manera de que pueda usar un bucle foreach en estos?La clase que estoy usando está sellada.

¿Fue útil?

Solución

foreach hace no requerir IEnumerable, contrario a la creencia popular.Todo lo que requiere es un método GetEnumerator que devuelve cualquier objeto que tenga el método MoveNext y la propiedad get Current con las firmas correspondientes.

/EDITAR:En tu caso, sin embargo, no tienes suerte.Sin embargo, puedes envolver trivialmente tu objeto para hacerlo enumerable:

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))
    …;

/EDITAR:El siguiente método, propuesto por varias personas, no no funciona si el método devuelve un IEnumerator:

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

Otros consejos

Re:Si foreach no requiere un contrato de interfaz explícito, ¿encuentra GetEnumerator mediante la reflexión?

(No puedo comentar porque no tengo una reputación lo suficientemente alta).

Si estás insinuando tiempo de ejecución reflexión entonces no.Lo hace todo en tiempo de compilación, otro hecho menos conocido es que también verifica si el objeto devuelto que podría Implementar IEnumerator es desechable.

Para ver esto en acción, considere este fragmento (ejecutable).


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 acuerdo a MSDN:

foreach (type identifier in expression) statement

donde la expresión es:

Colección de objetos o expresión de matriz.El tipo de elemento de recolección debe ser convertible en el tipo de identificador.No use una expresión que evalúe a NULL.Evalúa un tipo que implementa IEnumerable o un tipo que declara un método de getenumerator.En el último caso, Getenumerator debe devolver un tipo que implementa IEnumerator o declara todos los métodos definidos en IEnumerator.

Respuesta corta:

Necesitas una clase con un método llamado ObtenerEnumerador, que devuelve el IEnumerator que ya tienes.Logre esto con un simple envoltorio:

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

Desde el Especificación del lenguaje C#:

El procesamiento de tiempo de compilación de una declaración foreach determina primero el tipo de colección, el tipo de enumerador y el tipo de elemento de la expresión.Esta determinación procede de la siguiente manera:

  • Si el tipo X de expresión es un tipo de matriz, entonces hay una conversión de referencia implícita de x a la interfaz System.Collections.ienumerable (ya que System.Array implementa esta interfaz).El tipo de colección es el sistema.collections.ienumerable interfaz, el tipo de enumerador es el sistema.collections.ienumerator interfaz y el tipo de elemento es el tipo de elemento del tipo de matriz X.

  • De lo contrario, determine si el tipo X tiene un método Getenumerator apropiado:

    • Realice la búsqueda de miembros en el tipo X con el identificador getenumerator y sin argumentos de tipo.Si la búsqueda del miembro no produce una coincidencia, o produce una ambigüedad, o produce una coincidencia que no es un grupo de métodos, verifique una interfaz enumerable como se describe a continuación.Se recomienda emitir una advertencia si la búsqueda de miembros produce algo excepto un grupo de métodos o no coincide.

    • Realice una resolución de sobrecarga utilizando el grupo de métodos resultantes y una lista de argumentos vacías.Si la resolución de sobrecarga no resulta en métodos aplicables, resulta en una ambigüedad o da como resultado un mejor método, pero ese método es estático o no público, verifique una interfaz enumerable como se describe a continuación.Se recomienda emitir una advertencia si la resolución de sobrecarga produce algo excepto un método de instancia pública inequívoca o no hay métodos aplicables.

    • Si el tipo de retorno E del método Getenumerator no es una clase, estructura o tipo de interfaz, se produce un error y no se toman más pasos.

    • La búsqueda de miembros se realiza en E con el identificador actual y sin argumentos de tipo.Si la búsqueda del miembro no produce coincidencia, el resultado es un error, o el resultado es algo excepto una propiedad de instancia pública que permite la lectura, se produce un error y no se toman más pasos.

    • La búsqueda de miembros se realiza en E con el identificador MoveNext y sin argumentos de tipo.Si la búsqueda del miembro no produce coincidencia, el resultado es un error, o el resultado es algo excepto un grupo de métodos, se produce un error y no se toman más pasos.

    • La resolución de sobrecarga se realiza en el grupo de métodos con una lista de argumentos vacías.Si la resolución de sobrecarga no resulta en métodos aplicables, da como resultado una ambigüedad o resulta en un solo método único, pero ese método es estático o no público, o su tipo de retorno no es BOOL, se produce un error y no se toman más pasos.

    • El tipo de colección es x, el tipo de enumerador es E y el tipo de elemento es el tipo de propiedad actual.

  • De lo contrario, busque una interfaz enumerable:

    • Si hay exactamente un tipo T tal que hay una conversión implícita de X a la interfaz System.Collections.Generic.ienumerableu003CT> , entonces el tipo de colección es esta interfaz, el tipo de enumerador es la interfaz system.collections.generic.ienumeratoru003CT> , y el tipo de elemento es T.

    • De lo contrario, si hay más de uno de esos tipos T, entonces se produce un error y no se toman más pasos.

    • De lo contrario, si hay una conversión implícita de x al sistema.collections.ienumerable interfaz, entonces el tipo de colección es esta interfaz, el tipo de enumerador es la interfaz system.collections.ienumerator, y el tipo de elemento es objeto.

    • De lo contrario, se produce un error y no se toman más medidas.

No estrictamente.Siempre que la clase tenga los miembros GetEnumerator, MoveNext, Reset y Current requeridos, funcionará con foreach

No, no lo necesitas y ni siquiera necesitas un método GetEnumerator, por ejemplo:

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

que se llama de esta manera:

Counter cnt = new Counter();

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

Siempre puedes ajustarlo y, además, para que sea "foreachable", solo necesitas tener un método llamado "GetEnumerator" con la firma adecuada.


class EnumerableAdapter
{
  ExternalSillyClass _target;

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

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

}

Dada la clase X con los métodos A y B que devuelven IEnumerable, podrías usar un foreach en la clase como este:

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

// or

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

Presumiblemente, el significado de los enumerables devueltos por A y B está bien definido.

@Brian:No estoy seguro de intentar recurrir al valor de retorno de la llamada del método o la clase en sí, si lo que desea es la clase, lo convierte en una matriz que puede usar con foreach.

Para que una clase sea utilizable con foeach todo lo que necesita hacer es tener un método público que devuelva un IEnumerator llamado GetEnumerator(), eso es todo:

Tome la siguiente clase, no implementa IEnumerable o IEnumerator:

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

alternativamente se podría escribir el método GetEnumerator():

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

Cuando se usa en un foreach (tenga en cuenta que no se usa ningún contenedor, solo una instancia de clase):

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

huellas dactilares:

1 2 3 4 5 6

El tipo solo requiere tener un método público/no estático/no genérico/sin parámetros llamado GetEnumerator que debería devolver algo que tenga un público MoveNext método y un público Current propiedad.Según recuerdo en algún lugar del Sr. Eric Lippert, esto fue diseñado para adaptarse a la era pre genérica tanto para la seguridad del tipo como para los problemas de rendimiento relacionados con el boxeo en el caso de los tipos de valor.

Por ejemplo esto 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
{

}

Luego el compilador lo traduce a:

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 la sección 8.8.4 de la especificación.


Algo que vale la pena señalar es la precedencia del enumerador involucrada: es como si tuvieras un public GetEnumerator método, entonces esa es la opción predeterminada de foreach independientemente de quién lo implemente.Por ejemplo:

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

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

    }
}

(Si no tiene una implementación pública (es decir, solo una implementación explícita), entonces la prioridad es la siguiente: IEnumerator<T> > IEnumerator.)

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top