Domanda

Questo è in C #, ho una classe che sto usando dalla DLL di qualcun altro. Non implementa IEnumerable ma ha 2 metodi che restituiscono un IEnumerator. C'è un modo in cui posso usare un ciclo foreach su questi. La classe che sto usando è sigillata.

È stato utile?

Soluzione

foreach non richiede IEnumerable, contrariamente alla credenza popolare. Tutto ciò che serve è un metodo GetEnumerator che restituisca qualsiasi oggetto che abbia il metodo MoveNext e la proprietà get Current con le firme appropriate.

/ EDIT: nel tuo caso, tuttavia, sei sfortunato. Puoi avvolgere banalmente il tuo oggetto, tuttavia, per renderlo enumerabile:

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: il seguente metodo, proposto da più persone, non funziona se il metodo restituisce un IEnumerator:

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

Altri suggerimenti

Ri: Se foreach non richiede un contratto di interfaccia esplicito, trova GetEnumerator usando reflection?

(Non posso commentare poiché non ho una reputazione abbastanza alta.)

Se stai implicando la runtime riflessione, allora no. Fa tutto il tempo necessario, un altro fatto meno noto è che controlla anche se l'oggetto restituito che potrebbe Implementare IEnumerator è usa e getta.

Per vederlo in azione, considera questo frammento (eseguibile).


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

Secondo MSDN :

foreach (type identifier in expression) statement

dove espressione è:

  

Raccolta di oggetti o espressione di array.   Il tipo di elemento di raccolta   deve essere convertibile in identificatore   genere. Non usare un'espressione che   restituisce null. Valuta un tipo   che implementa IEnumerable o un tipo   che dichiara un metodo GetEnumerator.   In quest'ultimo caso, GetEnumerator   dovrebbe restituire un tipo che   implementa IEnumerator o dichiara tutto   i metodi definiti in IEnumerator.

Risposta breve:

È necessaria una classe con un metodo denominato GetEnumerator , che restituisce l'IEnumerator già in uso. Ottieni questo con un semplice wrapper:

class ForeachWrapper
{
  private IEnumerator _enumerator;

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

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

Utilizzo:

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

Dal Specifiche del linguaggio C # :

  

L'elaborazione in fase di compilazione di a   L'istruzione foreach determina innanzitutto la   tipo di raccolta, tipo di enumeratore e   tipo di elemento dell'espressione. Questo   la determinazione procede come segue:

     
      
  • Se il tipo X dell'espressione è un tipo di array, è implicito   conversione di riferimento da X a   System.Collections.IEnumerable   interfaccia (da System.Array   implementa questa interfaccia). Il   il tipo di raccolta è il   System.Collections.IEnumerable   interfaccia, il tipo di enumeratore è il   System.Collections.IEnumerator   interfaccia e il tipo di elemento è il   tipo di elemento dell'array tipo X.

  •   
  • Altrimenti, determinare se il tipo X ha un valore appropriato   Metodo GetEnumerator:

         
        
    • Esegue la ricerca dei membri sul tipo X con identificativo GetEnumerator e no   digita argomenti. Se il membro cerca   non produce una corrispondenza, o no   produce un'ambiguità o produce a   corrispondenza che non è un gruppo di metodi,   cerca un'interfaccia enumerabile come   descritto sotto. È raccomandato   che venga emesso un avviso se membro   la ricerca produce qualsiasi cosa tranne a   gruppo di metodi o nessuna corrispondenza.

    •   
    • Esegue la risoluzione di sovraccarico usando il gruppo di metodi risultante e un   elenco di argomenti vuoto. Se sovraccarico   la risoluzione risulta non applicabile   metodi, si traduce in un'ambiguità o   risulta in un unico metodo migliore ma   quel metodo è statico o no   pubblico, controlla un elenco   interfaccia come descritto di seguito. È   ha raccomandato l'emissione di un avviso   se la risoluzione di sovraccarico produce   tutto tranne un pubblico inequivocabile   metodo di istanza o non applicabile   metodi.

    •   
    • Se il tipo restituito E del metodo GetEnumerator non è una classe,   tipo di struttura o interfaccia, un errore è   prodotto e non ci sono ulteriori passaggi   preso.

    •   
    • La ricerca dei membri viene eseguita su E con l'identificatore Current e no   digita argomenti. Se il membro cerca   non produce alcuna corrispondenza, il risultato è un   errore o il risultato è qualsiasi cosa   tranne una proprietà di istanza pubblica che   consente la lettura, viene generato un errore   e non vengono prese ulteriori misure.

    •   
    • La ricerca dei membri viene eseguita su E con l'identificatore MoveNext e no   digita argomenti. Se il membro cerca   non produce alcuna corrispondenza, il risultato è un   errore o il risultato è qualsiasi cosa   tranne un gruppo di metodi, un errore è   prodotto e non ci sono ulteriori passaggi   preso.

    •   
    • La risoluzione di sovraccarico viene eseguita sul gruppo di metodi con un valore vuoto   elenco di argomenti. Se risoluzione di sovraccarico   non comporta metodi applicabili,   risulta in un'ambiguità o risulta in   un singolo metodo migliore ma quel metodo   è statico o non pubblico o relativo   il tipo restituito non è bool, un errore è   prodotto e non ci sono ulteriori passaggi   preso.

    •   
    • Il tipo di raccolta è X, il tipo di enumeratore è E e l'elemento   tipo è il tipo di corrente   Proprietà.

    •   
  •   
  • Altrimenti, controlla un'interfaccia enumerabile:

         
        
    • Se esiste esattamente un tipo T tale che esiste un implicito   conversione da X all'interfaccia   & System.Collections.Generic.IEnumerable lt; & T gt ;,   quindi il tipo di raccolta è questo   interfaccia, il tipo di enumeratore è il   interfaccia   & System.Collections.Generic.IEnumerator lt; & T gt ;,   e il tipo di elemento è T.

    •   
    • Altrimenti, se esiste più di uno di questi tipi T, allora c'è un errore   prodotto e non ci sono ulteriori passaggi   preso.

    •   
    • Altrimenti, se esiste un impliconversione cit da X a   System.Collections.IEnumerable   interfaccia, quindi il tipo di raccolta è   questa interfaccia, il tipo di enumeratore è   l'interfaccia   System.Collections.IEnumerator e   il tipo di elemento è object.

    •   
    • Altrimenti, viene prodotto un errore e non vengono prese ulteriori misure.

    •   
  •   

Non rigorosamente. Finché la classe ha i membri GetEnumerator, MoveNext, Reset e Current richiesti, funzionerà con foreach

No, non è necessario e non è nemmeno necessario un metodo GetEnumerator, ad esempio:

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

che si chiama in questo modo:

Counter cnt = new Counter();

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

Potresti sempre avvolgerlo e, a parte, essere " foreachable " devi solo avere un metodo chiamato " GetEnumerator " con la firma corretta.


class EnumerableAdapter
{
  ExternalSillyClass _target;

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

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

}

Data la classe X con i metodi A e B che restituiscono entrambi IEnumerable, puoi usare un foreach sulla classe in questo modo:

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

// or

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

Presumibilmente il significato per gli enumerabili restituiti da A e B è ben definito.

@Brian: non sei sicuro di provare a ricorrere al valore restituito dalla chiamata del metodo o alla classe stessa, Se quello che vuoi è la classe, rendila un array che puoi usare con foreach.

Affinché una classe sia utilizzabile con foeach tutto ciò che deve fare è avere un metodo pubblico che ritorni e IEnumerator chiamato GetEnumerator (), tutto qui:

Prendi la seguente classe, non 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;
        }
    }
}

in alternativa è possibile scrivere il metodo GetEnumerator ():

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

Se utilizzato in foreach (si noti che non viene utilizzato il wrapper no, solo un'istanza di classe):

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

stampe:

  

1 2 3 4 5 6

Il tipo richiede solo di avere un metodo pubblico / non statico / non generico / senza parametri denominato GetEnumerator che dovrebbe restituire qualcosa che ha un metodo MoveNext pubblico e una proprietà Current pubblica. Mentre ricordo che Eric Lippert da qualche parte, questo è stato progettato in modo da adattarsi all'era pre generica sia per la sicurezza dei tipi che per i problemi di prestazione relativi alla boxe in caso di tipi di valore.

Ad esempio funziona:

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
{

}

Questo viene quindi tradotto dal compilatore in:

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

Dalla sezione 8.8.4 delle specifiche .


Una cosa degna di nota è la precedenza dell'enumeratore coinvolto: è come se tu avessi un metodo public GetEnumerator, quindi questa è la scelta predefinita di foreach indipendentemente da chi lo sta implementando. Ad esempio:

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

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

    }
}

( Se non hai un'implementazione pubblica (ovvero solo un'implementazione esplicita), la precedenza è come IEnumerator<T> > IEnumerator. )

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top