Hat Klasse müssen IEnumerable implementieren Foreach zu verwenden
-
02-07-2019 - |
Frage
Dies ist in C #, ich eine Klasse, die ich von einigen anderen DLL verwenden. Dabei spielt es keine IEnumerable implementieren, sondern hat zwei Methoden, die einen IEnumerator Pass zurück. Gibt es eine Möglichkeit ich eine foreach-Schleife auf diese nutzen kann. Die Klasse Ich benutze ist versiegelt.
Lösung
foreach
tut nicht erfordert IEnumerable
, entgegen der landläufigen Meinung. Alles was es braucht ist eine Methode GetEnumerator
, die jedes Objekt zurückgibt, das die Methode MoveNext
und die get-Eigenschaft Current
mit den entsprechenden Unterschriften hat.
/ EDIT: In Ihrem Fall jedoch, bist du kein Glück. Sie können Ihr Objekt trivialer wickeln, jedoch machen es 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))
…;
/ EDIT: Die folgende Methode, die von mehreren Personen vorgeschlagen, tut nicht Arbeit, wenn die Methode einen IEnumerator
:
foreach (var yz in yourObj.MethodA())
…;
Andere Tipps
Re: Wenn foreach keinen expliziten Schnittstellenvertrag verlangt, es findet GetEnumerator Reflexion mit?
(Ich kann nicht sagen, da ich nicht über eine ausreichend hohe Reputation.)
Wenn Sie impliziert Laufzeit Reflexion dann nicht. Es macht alles compiletime, andere weniger bekannte Tatsache ist, dass es auch zu überprüfen, ob das zurückgegebene Objekt, dass könnte Implementieren Sie IEnumerator Einweg ist.
Um dies zu sehen in Aktion betrachtet diese (runnable) Schnipsel.
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);
}
}
}
Nach MSDN :
foreach (type identifier in expression) statement
Dabei gilt Ausdruck:
Objektsammlung oder Array-Ausdruck. Der Typ des Sammelelement muss dem Identifikator konvertierbar sein Art. Verwenden Sie keinen Ausdruck verwenden, die wertet auf null. Wertet auf einen Typ daß implementiert IEnumerable oder einen Typ dass deklariert eine Methode GetEnumerator. Im letzteren Fall GetEnumerator sollte entweder einen Typ zurückgeben, IEnumerator implementiert oder erklärt, alle die Methoden definiert in IEnumerator.
Kurze Antwort:
Sie müssen eine Klasse mit einer Methode namens GetEnumerator , die die IEnumerator zurück Sie bereits haben. Sie erreicht dies mit einem einfachen Wrapper:
class ForeachWrapper
{
private IEnumerator _enumerator;
public ForeachWrapper(Func<IEnumerator> enumerator)
{
_enumerator = enumerator;
}
public IEnumerator GetEnumerator()
{
return _enumerator();
}
}
Verbrauch:
foreach (var element in new ForeachWrapper(x => myClass.MyEnumerator()))
{
...
}
Aus dem C # Language Specification :
Die Kompilierung-Verarbeitung eines foreach-Anweisung zuerst bestimmt die Kollektionstyp, Enumeratortyp und Elementtyp des Ausdrucks. Diese Bestimmung geht wie folgt vor:
Wenn der Typ X des Ausdrucks ein Array-Typ, dann gibt es eine implizite Referenz Umwandlung von X zu dem System.Collections.IEnumerable Schnittstelle (seit System.Array implementiert diese Schnittstelle). Das Kollektionstyp ist die System.Collections.IEnumerable Schnittstelle, der Enumerator Typ ist die System.Collections.IEnumerator Schnittstelle und der Elementtyp ist das Elementtyp des Array-Typ X.
Ansonsten bestimmen, ob der Typ X ein geeignetes hat GetEnumerator-Methode:
Führen Sie Membersuche nach Typ X mit der Kennung GetEnumerator und keine Geben Sie Argumente. Wenn die Membersuche produziert nicht ein Spiel, oder es erzeugt eine Zweideutigkeit, oder erzeugt ein Spiel, das kein Verfahren Gruppe ist, Check für eine zählbare Schnittstelle nachstehend beschrieben. Es wird empfohlen dass eine Warnung, wenn das Mitglied ausgestellt Lookup produziert nichts außer einem Verfahren Gruppe oder kein Spiel.
Überladungsauflösung Führen Sie die resultierende Methodengruppe mit und ein leere Argumentliste. wenn Überlastung Auflösung führt zu keiner anwendbar Verfahren, führt zu einer Zweideutigkeit, oder Ergebnisse in einer einzigen besten Methode, aber daß Verfahren ist entweder statisch oder nicht, Öffentlichkeit, überprüfen Sie für eine zählbare eine Schnittstelle, wie unten beschrieben. Es ist empfohlen, dass eine Warnung ausgegeben werden, wenn die Überladungsauflösung erzeugt alles außer einer eindeutigen öffentlichen Instanzmethode oder nicht anwendbar Methoden.
Wenn der Rückgabetyp E der GetEnumerator-Methode nicht eine Klasse ist, struct oder Schnittstellentyp, ist ein Fehler, erzeugt und es werden keine weiteren Schritte genommen.
Membersuche auf E mit der Kennung Strom durchgeführt und keine Geben Sie Argumente. Wenn die Membersuche keine Übereinstimmung ergibt, ist das Ergebnis ein Fehler oder das Ergebnis ist alles außer einer öffentlichen Instanz Eigenschaft, dass ermöglicht das Lesen, wird ein Fehler erzeugt und keine weiteren Schritte unternommen werden.
Membersuche auf E mit der Kennung durchgeführt und kein Movenext Geben Sie Argumente. Wenn die Membersuche keine Übereinstimmung ergibt, ist das Ergebnis ein Fehler oder das Ergebnis ist alles mit Ausnahme eines Verfahrens Gruppe, ein Fehler ist, erzeugt und es werden keine weiteren Schritte genommen.
Die Überladungsauflösung wird mit einem leeren auf der Methodengruppe durchgeführt Argumentliste. Wenn die Überladungsauflösung Ergebnisse in keine anwendbare Methoden, führt zu einer Zweideutigkeit oder Ergebnisse in eine einzelne beste Methode, aber das Verfahren ist entweder statisch oder nicht öffentlich oder sein Rückgabetyp ist nicht Bool, ein Fehler ist erzeugt und es werden keine weiteren Schritte genommen.
Der Kollektionstyp X ist, ist der Enumerator Typ E, und das Element Typ ist der Typ des aktuellen Eigenschaft.
Andernfalls überprüfen Sie für eine zählbare Schnittstelle:
Wenn es genau einen Typ T, so ist, dass es eine implizite ist Umwandlung von X in die Schnittstelle System.Collections.Generic.IEnumerable
, dann ist die Sammlung Typ dieser Schnittstelle, der Enumerator Typ ist die Schnittstelle System.Collections.Generic.IEnumerator , und der Elementtyp ist T. Ansonsten, wenn es mehr als einen solchen Typ T ist, dann ist ein Fehler erzeugt und es werden keine weiteren Schritte genommen.
Ansonsten i, wenn ess eine implizite Umwandlung von X zu dem System.Collections.IEnumerable Schnittstelle, dann wird der Sammeltyp ist Diese Schnittstelle ist die Enumeratortyp die Schnittstelle System.Collections.IEnumerator und der Elementtyp ist Objekt.
Ansonsten wird ein Fehler erzeugt und keine weiteren Schritte unternommen werden.
Nicht streng. Solange die Klasse die erforderliche GetEnumerator hat, Movenext, Reset und aktuelle Mitglieder, wird es mit foreach arbeiten
Nein, tun Sie nicht und Sie haben nicht einmal eine GetEnumerator-Methode benötigen, z.
class Counter
{
public IEnumerable<int> Count(int max)
{
int i = 0;
while (i <= max)
{
yield return i;
i++;
}
yield break;
}
}
, die so genannt wird:
Counter cnt = new Counter();
foreach (var i in cnt.Count(6))
{
Console.WriteLine(i);
}
Sie können immer es wickeln und als beiseite zu sein „foreachable“ Sie brauchen nur ein Verfahren haben „GetEnumerator“ mit der richtigen Signatur bezeichnet.
class EnumerableAdapter
{
ExternalSillyClass _target;
public EnumerableAdapter(ExternalSillyClass target)
{
_target = target;
}
public IEnumerable GetEnumerator(){ return _target.SomeMethodThatGivesAnEnumerator(); }
}
Bei Klasse X mit Methoden A und B, die beide IEnumerable, könnten Sie eine foreach auf die Klasse wie folgt verwendet werden:
foreach (object y in X.A())
{
//...
}
// or
foreach (object y in X.B())
{
//...
}
Vermutlich ist die Bedeutung für die enumerables von A und B zurückgegeben werden, gut definiert.
@ Brian: Nicht sicher, dass Sie Schleife versuchen, über den Wert der Rückkehr von Methodenaufruf oder die Klasse selbst, Wenn das, was Sie wollen die Klasse ist dann durch ein Array machen Sie mit foreach verwenden können.
Für eine Klasse verwendbar zu sein mit foeach alle es tun muss, ist eine öffentliche Methode, die zurückgibt und IEnumerator namens GetEnumerator (), das ist es:
Nehmen Sie die folgende Klasse, es ist nicht IEnumerable oder IEnumerator nicht implementiert:
public class Foo
{
private int[] _someInts = { 1, 2, 3, 4, 5, 6 };
public IEnumerator GetEnumerator()
{
foreach (var item in _someInts)
{
yield return item;
}
}
}
alternativ das GetEnumerator () -Methode geschrieben werden:
public IEnumerator GetEnumerator()
{
return _someInts.GetEnumerator();
}
Wenn in einer foreach verwendet (Beachten Sie, dass der nicht-Wrapper verwendet wird, nur eine Klasseninstanz):
foreach (int item in new Foo())
{
Console.Write("{0,2}",item);
}
druckt:
1 2 3 4 5 6
Der Typ benötigt nur eine Öffentlichkeit haben / nicht-statischen / nicht-generic / parameterlos Methode namens GetEnumerator
, die etwas zurückgeben sollen, die ein öffentliches MoveNext
Verfahren und eine öffentliche Current
Eigenschaft hat. Als ich Herrn Eric Lippert irgendwo erinnern, dieses entworfen wurde, um vor generic Ära sowohl für Typsicherheit und Boxen im Zusammenhang von Leistungsproblemen bei Werttypen.
Zum Beispiel das funktioniert:
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
{
}
Dies ist dann durch den Compiler zu übersetzt wird:
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();
}
Von 8.8.4 Abschnitt der Spezifikation .
Etwas erwähnenswert ist der Enumerator Vorrang beteiligt - es geht so, wenn Sie eine public GetEnumerator
Methode haben, dann, dass die Standardauswahl von foreach
ist unabhängig davon, wer sie durchführt. Zum Beispiel:
class Test : IEnumerable<int>
{
public SomethingEnumerator GetEnumerator()
{
//this one is called
}
IEnumerator<int> IEnumerable<int>.GetEnumerator()
{
}
}
( Wenn Sie (dh nur explizite Implementierung) kein öffentliches Umsetzung haben, dann Vorrang geht wie IEnumerator<T>
> IEnumerator
. )