web-dev-qa-db-ger.com

IEnumerable vs IReadonlyCollection vs ReadonlyCollection zum Anzeigen eines Listenmitglieds

Ich habe einige Stunden damit verbracht, mir Gedanken darüber zu machen, wie man Listenmitglieder bloßstellt. In einer ähnlichen Frage wie meiner gab John Skeet eine hervorragende Antwort. Schauen Sie doch einfach mal rein.

ReadOnlyCollection oder IEnumerable zum Verfügbarmachen von Mitgliedersammlungen?

Normalerweise bin ich ziemlich paranoid, Listen zu veröffentlichen, besonders wenn Sie eine API entwickeln.

Ich habe immer IEnumerable zum Anzeigen von Listen verwendet, da es ziemlich sicher ist und so viel Flexibilität bietet. Lassen Sie mich hier ein Beispiel verwenden

public class Activity
{
    private readonly IList<WorkItem> workItems = new List<WorkItem>();

    public string Name { get; set; }

    public IEnumerable<WorkItem> WorkItems
    {
        get
        {
            return this.workItems;
        }
    }

    public void AddWorkItem(WorkItem workItem)
    {
        this.workItems.Add(workItem);
    }
}

Wer gegen einen IEnumerable codiert, ist hier ganz sicher. Wenn ich mich später dazu entscheide, eine geordnete Liste oder etwas anderes zu verwenden, bricht keiner der Codes und es ist immer noch nett. Der Nachteil ist, dass IEnumerable auf eine Liste außerhalb dieser Klasse zurückgesetzt werden kann.

Aus diesem Grund verwenden viele Entwickler ReadOnlyCollection, um ein Mitglied verfügbar zu machen. Dies ist ziemlich sicher, da es nie wieder in eine Liste umgewandelt werden kann. Ich bevorzuge IEnumerable, da es mehr Flexibilität bietet, falls ich jemals etwas anderes als eine Liste implementieren möchte.

Ich habe mir eine neue Idee ausgedacht, die mir besser gefällt. Verwenden von IReadOnlyCollection

public class Activity
{
    private readonly IList<WorkItem> workItems = new List<WorkItem>();

    public string Name { get; set; }

    public IReadOnlyCollection<WorkItem> WorkItems
    {
        get
        {
            return new ReadOnlyCollection<WorkItem>(this.workItems);
        }
    }

    public void AddWorkItem(WorkItem workItem)
    {
        this.workItems.Add(workItem);
    }
}

Ich bin der Meinung, dass dies einen Teil der Flexibilität von IEnumerable beibehält und recht gut gekapselt ist.

Ich habe diese Frage gestellt, um Anregungen für meine Idee zu erhalten. Bevorzugen Sie diese Lösung gegenüber IEnumerable? Halten Sie es für besser, einen konkreten Rückgabewert von ReadOnlyCollection zu verwenden? Dies ist eine ziemliche Debatte, und ich möchte versuchen, herauszufinden, welche Vor- und Nachteile wir alle haben können.

Vielen Dank im Voraus für Ihre Eingabe.

[~ # ~] edit [~ # ~]

Zunächst einmal vielen Dank, dass Sie so viel zur Diskussion beigetragen haben. Ich habe mit Sicherheit eine Menge von jedem gelernt und möchte mich aufrichtig bei Ihnen bedanken.

Ich füge einige zusätzliche Szenarien und Informationen hinzu.

Es gibt einige häufige Fallstricke bei IReadOnlyCollection und IEnumerable.

Betrachten Sie das folgende Beispiel:

public IReadOnlyCollection<WorkItem> WorkItems
{
    get
    {
        return this.workItems;
    }
}

Das obige Beispiel kann zurück in eine Liste umgewandelt und mutiert werden, obwohl die Schnittstelle schreibgeschützt ist. Das Interface garantiert trotz seines Namensvetters keine Unveränderlichkeit. Es liegt an Ihnen, eine unveränderliche Lösung bereitzustellen, daher sollten Sie eine neue ReadOnlyCollection zurückgeben. Durch das Erstellen einer neuen Liste (im Wesentlichen einer Kopie) ist der Zustand Ihres Objekts sicher und zuverlässig.

Richiban sagt es am besten in seinem Kommentar: Interface garantiert nur, was etwas kann, nicht, was es nicht kann

Unten finden Sie ein Beispiel.

public IEnumerable<WorkItem> WorkItems
{
    get
    {
        return new List<WorkItem>(this.workItems);
    }
}

Das Obige kann gewirkt und mutiert werden, aber Ihr Objekt ist immer noch unveränderlich.

Eine weitere Anweisung außerhalb des Rahmens wären Auflistungsklassen. Folgendes berücksichtigen:

public class Bar : IEnumerable<string>
{
    private List<string> foo;

    public Bar()
    {
        this.foo = new List<string> { "123", "456" };
    }

    public IEnumerator<string> GetEnumerator()
    {
        return this.foo.GetEnumerator();
    }

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

Die obige Klasse kann Methoden enthalten, um zu ändern, wie Sie es möchten, aber Ihr Objekt kann niemals in eine Liste beliebiger Art umgewandelt und geändert werden.

Carsten Führmann macht eine fantastische Aussage zu Rendite-Rendite-Aussagen in IEnumerables.

Nochmals vielen Dank.

53
Marcel-Is-Hier

Apropos Klassenbibliotheken, ich denke IReadOnly * ist wirklich nützlich und ich denke, du machst es richtig :)

Alles dreht sich um unveränderliche Sammlungen ... Bevor es nur unveränderliche und vergrößerte Arrays gab, war es eine große Aufgabe. Deshalb hat .net beschlossen, eine andere veränderbare Sammlung in das Framework aufzunehmen, die das hässliche Zeug für Sie implementiert, aber IMHO haben sie es nicht getan. ' • Geben Sie die richtige Richtung für unveränderliche Daten an, die äußerst nützlich sind, insbesondere in einem Szenario mit hoher Parallelität, in dem das Teilen von veränderlichen Daten immer eine PITA ist.

Wenn Sie andere heutige Sprachen wie Objective-C überprüfen, werden Sie feststellen, dass die Regeln tatsächlich vollständig invertiert sind! Sie tauschen ziemlich immer unveränderliche Auflistungen zwischen verschiedenen Klassen aus, mit anderen Worten, die Schnittstelle macht nur unveränderliche Auflistungen verfügbar, und intern verwenden sie veränderbare Auflistungen (ja, sie haben es natürlich). wenn die Klasse eine zustandsbehaftete Klasse ist).

Diese kleine Erfahrung mit anderen Sprachen veranlasst mich zu der Annahme, dass .net-Listen so mächtig sind, aber die unveränderliche Sammlung war aus irgendeinem Grund da :)

In diesem Fall geht es nicht darum, dem Aufrufer einer Schnittstelle zu helfen, zu vermeiden, dass er den gesamten Code ändert, wenn Sie die interne Implementierung ändern, wie dies bei IList vs List der Fall ist, sondern bei IReadOnly *, dass Sie sich selbst schützen Klasse, um nicht richtig verwendet zu werden, um nutzlosen Schutzcode zu vermeiden, Code, den man manchmal auch nicht schreiben konnte (in der Vergangenheit musste ich in einem Teil des Codes einen Klon der vollständigen Liste zurückgeben, um dieses Problem zu vermeiden) .

16
Michael Denny

Ein wichtiger Aspekt scheint in den bisherigen Antworten zu fehlen:

Wenn ein IEnumerable<T> An den Aufrufer zurückgegeben wird, müssen sie die Möglichkeit berücksichtigen, dass das zurückgegebene Objekt ein "Lazy Stream" ist, z. eine Sammlung mit "Yield Return" gebaut. Das heißt, die Leistungsstrafe für das Erzeugen der Elemente des IEnumerable<T> Muss möglicherweise vom Aufrufer für jede Verwendung der IEnumerable bezahlt werden. (Das Produktivitätstool "Resharper" weist darauf hin, dass es sich um einen Codegeruch handelt.)

Im Gegensatz dazu signalisiert ein IReadOnlyCollection<T> Dem Anrufer, dass keine verzögerte Auswertung erfolgt. (Die Count -Eigenschaft im Gegensatz zur Count Erweiterungsmethode von IEnumerable<T> (Die von geerbt wird IReadOnlyCollection<T>, Also hat es auch die Methode), signalisiert Nicht-Faulheit. Und die Tatsache, dass es keine faulen Implementierungen von IReadOnlyCollection zu geben scheint.)

Dies gilt auch für Eingabeparameter, da das Anfordern eines IReadOnlyCollection<T> - anstelle eines IEnumerable<T> - Signals das die Methode benötigt, um die Auflistung mehrmals zu durchlaufen. Sicher, die Methode könnte aus dem IEnumerable<T> Eine eigene Liste erstellen und diese durchlaufen, aber da der Aufrufer möglicherweise bereits eine geladene Sammlung zur Hand hat, wäre es sinnvoll, diese zu nutzen, wann immer dies möglich ist. Wenn der Aufrufer nur einen IEnumerable<T> Zur Hand hat, muss er nur .ToArray() oder .ToList() zum Parameter hinzufügen.

IReadOnlyCollection verhindert nicht , dass der Aufrufer in einen anderen Auflistungstyp umgewandelt wird. Für einen solchen Schutz müsste man die Klasse ReadOnlyCollection<T> Verwenden.

Zusammenfassend kann gesagt werden, dass die only Funktion, die IReadOnlyCollection<T> Im Verhältnis zu IEnumerable<T> Ausführt, eine Count -Eigenschaft hinzufügt und damit signalisiert, dass keine Faulheit vorliegt.

63

Es scheint, dass Sie einfach eine entsprechende Schnittstelle zurückgeben können :

...
    private readonly List<WorkItem> workItems = new List<WorkItem>();

    // Usually, there's no need the property to be virtual 
    public virtual IReadOnlyList<WorkItem> WorkItems {
      get {
        return workItems;
      }
    }
...

Da das workItems -Feld tatsächlich List<T> Ist, besteht die natürliche Idee IMHO darin, die breiteste Schnittstelle freizulegen, die in diesem Fall IReadOnlyList<T> Ist

4
Dmitry Bychenko