13 Stimmen

Warum IEnumerable(T) implementieren, wenn ich nur EINEN GetEnumerator definieren kann?

Update : Ich bedanke mich für alle Kommentare, die im Wesentlichen aus einhelliger Ablehnung bestanden haben. Obwohl jeder Einwand berechtigt war, glaube ich, dass der letzte Nagel im Sarg war Anis scharfsinnige Beobachtung dass letztlich sogar die eine winzige Der Vorteil, den diese Idee angeblich bot - die Beseitigung von Standardcode - wurde durch die Tatsache zunichte gemacht, dass die Idee selbst ihre eigene Code aus dem Baukasten.

Also ja, ich bin überzeugt: Es wäre eine schlechte Idee.

Und nur um meine Würde ein wenig zu retten: Ich habe es vielleicht um des Argumentes willen hochgespielt, aber ich war nie wirklich von dieser Idee überzeugt - ich war nur neugierig zu hören, was andere dazu zu sagen hatten. Ehrlich.


Bevor Sie diese Frage als absurd abtun, möchte ich Sie bitten, Folgendes zu bedenken:

  1. IEnumerable<T> erbt von* IEnumerable , was bedeutet, dass jeder Typ, der IEnumerable<T> muss generell umsetzen beide IEnumerable<T>.GetEnumerator (ausdrücklich) IEnumerable.GetEnumerator . Im Grunde genommen handelt es sich um einen Standardcode.
  2. Sie können foreach über jeden Typ, der eine GetEnumerator Methode, solange diese Methode ein Objekt irgendeines Typs mit einer MoveNext Methode und ein Current Eigentum. Wenn Ihr Typ also definiert eine Methode mit der Signatur public IEnumerator<T> GetEnumerator() ist es legal, darüber zu enumerieren, indem man foreach .
  3. Offensichtlich gibt es eine Menge Code, für den die IEnumerable<T> Schnittstelle - z. B. grundsätzlich alle LINQ-Erweiterungsmethoden. Glücklicherweise kann man von einem Typ, den man foreach auf einen IEnumerable<T> ist trivial, wenn man die automatische Iterator-Generierung verwendet, die C# über die yield Stichwort.

Als ich das alles zusammenfasste, hatte ich die verrückte Idee: Wie wäre es, wenn ich einfach meine eigene Schnittstelle definiere, die so aussieht?

public interface IForEachable<T>
{
    IEnumerator<T> GetEnumerator();
}

Dann Wenn ich einen Typ definiere, der aufzählbar sein soll, implementiere ich diese Schnittstelle anstelle von IEnumerable<T> Dadurch entfällt die Notwendigkeit, zwei GetEnumerator Methoden (eine explizit). 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().
}

Endlich um diesen Typ mit Code kompatibel zu machen, der tut erwarten die IEnumerable<T> Schnittstelle kann ich einfach eine Erweiterungsmethode definieren, um von jeder IForEachable<T> zu einer IEnumerable<T> etwa so:

public static class ForEachableExtensions
{
    public static IEnumerable<T> AsEnumerable<T>(this IForEachable<T> source)
    {
        foreach (T item in source)
        {
            yield return item;
        }
    }
}

Ich habe den Eindruck, dass ich auf diese Weise Typen entwerfen kann, die in jeder Hinsicht als Implementierungen von IEnumerable<T> aber ohne das lästige explizite IEnumerable.GetEnumerator Umsetzung in jedem einzelnen Fall.

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 haltet ihr von dieser Idee? Übersehe ich einen Grund, warum dies ein Fehler wäre?

Mir ist klar, dass dies wahrscheinlich nach einer übermäßig komplexen Lösung für ein Problem aussieht, das eigentlich gar nicht so groß ist (ich bezweifle, dass jemand wirklich sich so sehr darum kümmert, dass man explizit die IEnumerable Schnittstelle); aber es ist mir gerade in den Sinn gekommen und ich sehe keine offensichtlichen Probleme, die dieser Ansatz mit sich bringen würde.

Im Allgemeinen gilt: Wenn ich eine moderate Menge an Code schreiben kann einmal um mir die Mühe zu ersparen, einen kleinen Teil des Codes schreiben zu müssen viele Male für mich ist es das wert.

*Ist das die richtige Terminologie? Ich zögere immer, wenn ich sage, dass eine Schnittstelle von einer anderen "erbt", da dies die Beziehung zwischen ihnen nicht richtig zu erfassen scheint. Aber vielleicht ist es richtig.

13voto

Reed Copsey Punkte 536986

Du übersiehst eine wichtige Sache -

Wenn Sie Ihr eigenes Interface implementieren, anstatt IEnumerable<T> wird Ihre Klasse nicht mit den Methoden des Frameworks funktionieren, die Folgendes erwarten IEnumerable<T> - können Sie weder LINQ verwenden noch Ihre Klasse zum Aufbau einer List<T> oder viele andere nützliche Abstraktionen.

Sie können dies, wie Sie erwähnen, über eine separate Verlängerungsmethode erreichen - dies ist jedoch mit Kosten verbunden. Durch die Verwendung einer Erweiterungsmethode zur Umwandlung in eine IEnumerable<T> Sie fügen eine weitere Abstraktionsebene hinzu, die erforderlich ist, um Ihre Klasse zu verwenden (was Sie viel häufiger tun werden, als die Klasse zu erstellen), und Sie verringern die Leistung (Ihre Erweiterungsmethode wird intern eine neue Klassenimplementierung erzeugen, was wirklich unnötig ist). Am wichtigsten ist jedoch, dass jeder andere Benutzer Ihrer Klasse (oder später Sie selbst) eine neue API erlernen muss, die nichts bewirkt - Sie erschweren die Verwendung Ihrer Klasse, indem Sie keine Standardschnittstellen verwenden, da dies den Erwartungen des Benutzers widerspricht.

9voto

Jon Skeet Punkte 1325502

Sie haben Recht: Es tut scheinen eine übermäßig komplexe Lösung für ein ziemlich einfaches Problem zu sein.

Außerdem wird für jeden Schritt der Iteration eine zusätzliche indirekte Ebene eingeführt. Wahrscheinlich kein Leistungsproblem, aber dennoch etwas unnötig, wenn ich nicht glaube, dass man wirklich etwas sehr Wichtiges gewinnt.

Auch wenn Ihre Erweiterungsmethode die Konvertierung beliebiger IForEachable<T> in eine IEnumerable<T> bedeutet dies, dass Ihr Typ selbst wird nicht eine generische Bedingung wie diese erfüllen:

public void Foo<T>(T collection) where T : IEnumerable

oder dergleichen. Indem Sie Ihre Konvertierung durchführen müssen, verlieren Sie die Möglichkeit, ein einzelnes Objekt als beide eine Implementierung von IEnumerable<T> der echte konkrete Typ.

Auch durch die Nichtumsetzung IEnumerable zählen Sie sich selbst nicht mehr zu den Initialisierern der Sammlung. (Ich implementiere manchmal IEnumerable ausdrücklich juste in die Sammlungsinitialisierung einsteigen und eine Ausnahme von GetEnumerator() .)

Oh, und Sie haben auch ein zusätzliches Stück Infrastruktur eingeführt, das allen anderen auf der Welt unbekannt ist, verglichen mit den riesigen Horden von C#-Entwicklern, die bereits wissen, wie IEnumerable<T> .

5voto

Rex M Punkte 138455

In unserer Codebasis gibt es wahrscheinlich mehr als 100 Instanzen von genau diesem Snippet:

IEnumerator IEnumerable.GetEnumerator()
{
    return this.GetEnumerator();
}

Und damit bin ich wirklich einverstanden. Es ist ein kleiner Preis für die volle Kompatibilität mit jedem anderen Stück .NET-Code, das jemals geschrieben wurde ;)

Die von Ihnen vorgeschlagene Lösung erfordert im Wesentlichen, dass jeder Verbraucher/Aufrufer Ihrer neuen Schnittstelle auch daran denken muss, eine spezielle Erweiterungsmethode aufzurufen, bevor sie nützlich .

5voto

Ani Punkte 107342

Verschieben Sie nicht einfach nur die Boilerplate an einen anderen Ort - vom Schreiben der IEnumerable.GetEnumerator Methode in jeder Klasse zum Aufruf Ihrer AsEnumerable Erweiterung jedes Mal, wenn ein IEnumerable<T> erwartet wird? Normalerweise würde ich erwarten, dass ein Aufzählungstyp viel öfter für Abfragen verwendet wird, als er geschrieben wird (nämlich genau einmal). Dies würde bedeuten, dass dieses Muster zu folgenden Ergebnissen führt im Durchschnitt eine Standardformulierung.

1voto

ChaosPandion Punkte 75527

Es ist so viel einfacher zu benutzen IEnumerable zum Nachdenken. Der Versuch, generische Schnittstellen über Reflection aufzurufen, ist sehr mühsam. Die Strafe für Boxing über IEnumerable geht durch den Overhead der Reflexion selbst verloren, warum also eine generische Schnittstelle verwenden? Als Beispiel kommt mir die Serialisierung in den Sinn.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X