Warum IEnumerable (T) implementieren, wenn ich nur ein GetEnumerator definieren?
-
03-10-2019 - |
Frage
Aktualisieren : Ich alle Kommentare zu schätzen wissen, die im Wesentlichen einig Opposition bestehen haben. Während jede Einwand gültig war, glaube ich, dass der ultimative Nagel im Sarg war Ani kluge Beobachtung , dass letztendlich auch der ein miniscule Vorteil, dass diese Idee angeblich angeboten - die Beseitigung von vorformulierten Code -. wurde durch die Tatsache negiert, dass die Idee selbst würde seinen eigenen Standardcode erfordert
Also ja, sollten Sie mich überzeugt. Es wäre eine schlechte Idee sein
Und nur um eine Art Bergungs meine Würde etwas: Ich habe es vielleicht gespielt Argument willen, aber ich war nie wirklich auf dieser Idee verkauft zu beginnen - nur neugierig zu hören, was andere hatten darüber zu sagen. Ehrlich.
Bevor Sie diese Frage als absurd abtun, frage ich Sie, folgendes zu beachten:
-
IEnumerable<T>
erbt von *IEnumerable
, was bedeutet, dass jede Art der GeräteIEnumerable<T>
allgemein implementieren müssen beideIEnumerable<T>.GetEnumerator
und (explizit)IEnumerable.GetEnumerator
. Dies entspricht im Wesentlichen auf Standardcode. - Sie können jede Art
foreach
über die eineGetEnumerator
Methode hat, solange diese Methode gibt ein Objekt eines Typs mit einemMoveNext
Verfahren und eineCurrent
Eigenschaft. Also, wenn Ihr Typ definiert ein Methode mit der Signaturpublic IEnumerator<T> GetEnumerator()
, ist es legal, sie aufzuzählen überforeach
verwenden. - Offensichtlich gibt es eine Menge Code gibt, die die
IEnumerable<T>
Schnittstelle benötigt - zum Beispiel, im Grunde alle die LINQ-Erweiterungsmethoden. Zum Glück, zu einer Art zu gehen, dass Sie auf einenforeach
IEnumerable<T>
auf können trivial ist die automatische Iterator Generation mit, dass C # liefert über dasyield
Stichwort.
Also, das alles zusammen setzen, ich hatte diese verrückte Idee: Was, wenn ich nur meine eigene Schnittstelle definieren, dass sieht wie folgt aus:
public interface IForEachable<T>
{
IEnumerator<T> GetEnumerator();
}
Dann , wenn ich eine Art definieren, dass ich will enumerable sein, ich implementieren diese Schnittstelle statt IEnumerable<T>
, entfällt die Notwendigkeit, zwei GetEnumerator
Methoden zu implementieren (eine ausdrückliche) . Zum Beispiel:
class NaturalNumbers : IForEachable<int>
{
public IEnumerator<int> GetEnumerator()
{
int i = 1;
while (i < int.MaxValue)
{
yield return (i++);
}
}
// Notice how I don't have to define a method like
// IEnumerator IEnumerable.GetEnumerator().
}
Schließlich , um diese Art mit Code kompatibel zu machen, dass hat die IEnumerable<T>
Schnittstelle erwartet, kann ich nur eine Erweiterungsmethode definiere von jedem IForEachable<T>
zu einem IEnumerable<T>
gehen etwa so:
public static class ForEachableExtensions
{
public static IEnumerable<T> AsEnumerable<T>(this IForEachable<T> source)
{
foreach (T item in source)
{
yield return item;
}
}
}
Es scheint mir, dass dies zu tun ermöglicht es mir, Arten zu entwerfen, die in jeder Hinsicht als Implementierungen von IEnumerable<T>
verwendbar sind, aber ohne diese lästigen explizite IEnumerable.GetEnumerator
Implementierung in jedem.
Zum Beispiel:
var numbers = new NaturalNumbers();
// I can foreach myself...
foreach (int x in numbers)
{
if (x > 100)
break;
if (x % 2 != 0)
continue;
Console.WriteLine(x);
}
// Or I can treat this object as an IEnumerable<T> implementation
// if I want to...
var evenNumbers = from x in numbers.AsEnumerable()
where x % 2 == 0
select x;
foreach (int x in evenNumbers.TakeWhile(i => i <= 100))
{
Console.WriteLine(x);
}
Was denkt ihr von dieser Idee? Bin ich aus irgendeinem Grund fehlt, warum dies ein Fehler sein würde?
Ich weiß, es scheint wahrscheinlich wie eine zu komplexer Lösung zu dem, was ist nicht so große Sache, mit zu beginnen (ich bezweifle, jemand wirklich Sorgen, dass viel darüber, dass explizit die IEnumerable
Schnittstelle definieren) ; aber es nur tauchte in meinem Kopf und ich bin keine offensichtlichen Probleme zu sehen, dass dieser Ansatz darstellen würde.
In der Regel, wenn ich eine moderate Menge an Code schreiben kann einmal mir die Mühe, sparen eine kleine Menge Code viele Male schreiben , mir , es lohnt sich.
* Ist das die richtige Terminologie zu bedienen? Ich bin immer zögerlich eine Schnittstelle „erbt von“ zu sagen, einem anderen, als dass nicht richtig scheinen, um die Beziehung zwischen ihnen zu erfassen. Aber vielleicht ist es richtig auf.
Lösung
Sind nicht nur Sie die vorformulierten bewegen woanders - von der IEnumerable.GetEnumerator
method Schreiben auf jede Klasse Ihre AsEnumerable
Erweiterung ein IEnumerable<T>
jedes Mal Aufruf erwartet? Normalerweise würde ich eine zählbare Art zur Abfrage weit mehr Zeit verwendet werden erwarten, als es geschrieben ist (was genau einmal ist). Dies würde bedeuten, dass dieses Muster führen mehr vorformulierten, im Durchschnitt.
Andere Tipps
Sie sind eine große Sache fehlt -
Wenn Sie eine eigene Schnittstelle statt IEnumerable<T>
implementieren, Ihre Klasse wird nicht die Arbeit mit den Rahmen Methoden IEnumerable<T>
erwarten - vor allem, werden Sie völlig unfähig sein LINQ zu verwenden, oder verwenden Sie Ihre Klasse eine List<T>
, oder viele andere nützliche Abstraktionen zu konstruieren .
Sie können dies erreichen, wie Sie über eine separate Erweiterungsmethode erwähnen, - aber dies ist mit Kosten verbunden. Durch die Verwendung eines Verlängerungsverfahren zu konvertieren zu einem IEnumerable<T>
, du bist das Hinzufügen eines weiteren Abstraktionsebene erforderlich, um die Klasse zu verwenden (die Sie FAR oft mehr tun, als die Klasse Authoring) und Sie verringern die Leistung (Ihre Erweiterungsmethode wird in der Tat, erzeugt intern eine neue Klasse Implementierung, die wirklich nicht notwendig ist). Am wichtigsten ist, jeder andere Benutzer Ihrer Klasse (oder später) muss eine neue API lernen, die nichts erreicht - Sie Ihre Klasse erschwert durch nicht unter Verwendung von Standard-Schnittstellen zu verwenden, da es die Benutzer-Erwartungen verletzt.
Du hast Recht. Es hat scheint eine zu komplexe Lösung für ein ziemlich einfaches Problem
Es führt auch eine zusätzliche Dereferenzierungsebene für jeden Schritt der Iteration. Wahrscheinlich kein Leistungsproblem, aber immer noch etwas unnötig, wenn ich glaube nicht, dass du wirklich etwas von großer Bedeutung gewinnt.
Auch, obwohl Ihre Erweiterung Methode können Sie jede IForEachable<T>
in eine IEnumerable<T>
konvertieren, es bedeutet, dass Ihre Art sich nicht eine generische Einschränkung wie folgt erfüllen:
public void Foo<T>(T collection) where T : IEnumerable
oder dergleichen. Im Grunde genommen, indem Sie Ihre Konvertierung, Sie verlieren die Fähigkeit, ein einzelnes Objekt als beide eine Implementierung von IEnumerable<T>
und die wirklichen konkreten Art zu behandeln.
Auch durch nicht IEnumerable
Implementierung, bist du selbst aus der Sammlung Initialisierungen zu zählen. (Ich manchmal implementieren IEnumerable
explizit nur zu opt in der Sammel Initialisierung, eine Ausnahme von GetEnumerator()
zu werfen.)
Oh, und Sie haben auch ein zusätzliches Bit der Infrastruktur eingeführt, die für alle anderen in der Welt nicht vertraut ist, verglichen mit den riesigen Horden von C # Entwickler, die bereits über IEnumerable<T>
kennen.
In unserer Code-Basis gibt es wahrscheinlich mehr als 100 Fälle dieser genauen Snippet:
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
Und ich bin wirklich mit dem OKAY. Es ist ein kleiner Preis zu zahlen für die volle Kompatibilität mit jedem anderen Stück .NET-Code jemals geschrieben;)
Ihre vorgeschlagene Lösung erfordert im Wesentlichen jeden Verbraucher / Anrufer Ihrer neuen Schnittstelle auch eine spezielle Erweiterung Methode zu nennen zu erinnern, bevor es ist nützlich .
Es ist so viel einfacher IEnumerable
für Reflexion zu verwenden. Der Versuch, generische Schnittstellen über Reflexion aufzurufen ist so ein Schmerz. Die Strafe des Boxens über IEnumerable
wird durch den Overhead der Reflexion verloren sich, warum so stört eine generische Schnittstelle? Als ein Beispiel, kommt Serialisierung in dem Sinne.
Ich denke, jeder hat bereits die technischen Gründen erwähnt, dies nicht zu tun, so dass ich dies in den Mix werden: benötigen Sie Ihren Benutzer AsEnumerable () auf Ihrer Sammlung aufrufen zu können, zählbare Erweiterungen verwenden würde verletzen das Prinzip der dest Überraschung.