wake-up-neo.com

ReadOnlyCollection oder IEnumerable zum Anzeigen von Mitgliedersammlungen?

Gibt es einen Grund, eine interne Auflistung als ReadOnlyCollection und nicht als IEnumerable bereitzustellen, wenn der aufrufende Code nur die Auflistung durchläuft?

class Bar
{
    private ICollection<Foo> foos;

    // Which one is to be preferred?
    public IEnumerable<Foo> Foos { ... }
    public ReadOnlyCollection<Foo> Foos { ... }
}


// Calling code:

foreach (var f in bar.Foos)
    DoSomething(f);

Wie ich es sehe, ist IEnumerable eine Teilmenge der Schnittstelle von ReadOnlyCollection und es erlaubt dem Benutzer nicht, die Sammlung zu ändern. Wenn also die IEnumberable-Schnittstelle ausreicht, ist dies die zu verwendende. Ist das eine richtige Argumentationsweise, oder vermisse ich etwas?

Danke/Erik

115
Erik Öjebo

Modernere Lösung

Sofern die interne Auflistung nicht veränderbar sein muss, können Sie die Zeichenfolge System.Collections.Immutable package, ändern Sie Ihren Feldtyp in eine unveränderbare Auflistung und legen Sie dann offen, dass dies direkt möglich ist, vorausgesetzt, Foo selbst ist natürlich unveränderlich.

Aktualisierte Antwort, um die Frage direkter zu beantworten

Gibt es einen Grund, eine interne Auflistung als ReadOnlyCollection und nicht als IEnumerable bereitzustellen, wenn der aufrufende Code nur die Auflistung durchläuft?

Es hängt davon ab, wie sehr Sie dem aufrufenden Code vertrauen. Wenn Sie die vollständige Kontrolle über alles haben, was dieses Mitglied jemals anrufen wird, und Sie Garantie sicherstellen, dass kein Code jemals verwendet wird:

ICollection<Foo> evil = (ICollection<Foo>) bar.Foos;
evil.Add(...);

in diesem Fall kann es nicht schaden, wenn Sie die Sammlung direkt zurücksenden. Im Allgemeinen versuche ich, ein bisschen paranoider zu sein.

Ebenso, wie du sagst: wenn du nur brauchstIEnumerable<T>, warum bindest du dich dann an etwas Stärkeres?

Ursprüngliche Antwort

Wenn Sie .NET 3.5 verwenden, können Sie vermeiden, eine Kopie zu erstellen und vermeiden, indem Sie einfach Skip aufrufen:

public IEnumerable<Foo> Foos {
    get { return foos.Skip(0); }
}

(Es gibt noch viele andere Möglichkeiten, einen einfachen Zeilenumbruch durchzuführen - das Schöne an Skip over Select/Where ist, dass es keinen Delegaten gibt, der für jede Iteration sinnlos ausgeführt werden kann.)

Wenn Sie .NET 3.5 nicht verwenden, können Sie einen sehr einfachen Wrapper schreiben, um dasselbe zu tun:

public static IEnumerable<T> Wrapper<T>(IEnumerable<T> source)
{
    foreach (T element in source)
    {
        yield return element;
    }
}
92
Jon Skeet

Wenn Sie nur die Sammlung durchlaufen müssen:

foreach (Foo f in bar.Foos)

dann reicht es, IEnumerable zurückzugeben.

Wenn Sie zufälligen Zugriff auf Artikel benötigen:

Foo f = bar.Foos[17];

dann wickeln Sie es in ReadOnlyCollection.

40

Wenn Sie dies tun, hindert nichts Ihre Aufrufer daran, die IEnumerable zurück in ICollection zu übertragen und dann zu ändern. ReadOnlyCollection entfernt diese Möglichkeit, obwohl es weiterhin möglich ist, über Reflektion auf die zugrunde liegende beschreibbare Sammlung zuzugreifen. Wenn die Sammlung klein ist, können Sie dieses Problem sicher und einfach umgehen, indem Sie stattdessen eine Kopie zurückgeben.

31
Stu Mackellar

Ich vermeide es, ReadOnlyCollection so oft wie möglich zu verwenden, es ist tatsächlich erheblich langsamer als nur eine normale Liste zu verwenden. Siehe dieses Beispiel:

List<int> intList = new List<int>();
        //Use a ReadOnlyCollection around the List
        System.Collections.ObjectModel.ReadOnlyCollection<int> mValue = new System.Collections.ObjectModel.ReadOnlyCollection<int>(intList);

        for (int i = 0; i < 100000000; i++)
        {
            intList.Add(i);
        }
        long result = 0;

        //Use normal foreach on the ReadOnlyCollection
        TimeSpan lStart = new TimeSpan(System.DateTime.Now.Ticks);
        foreach (int i in mValue)
            result += i;
        TimeSpan lEnd = new TimeSpan(System.DateTime.Now.Ticks);
        MessageBox.Show("Speed(ms): " + (lEnd.TotalMilliseconds - lStart.TotalMilliseconds).ToString());
        MessageBox.Show("Result: " + result.ToString());

        //use <list>.ForEach
        lStart = new TimeSpan(System.DateTime.Now.Ticks);
        result = 0;
        intList.ForEach(delegate(int i) { result += i; });
        lEnd = new TimeSpan(System.DateTime.Now.Ticks);
        MessageBox.Show("Speed(ms): " + (lEnd.TotalMilliseconds - lStart.TotalMilliseconds).ToString());
        MessageBox.Show("Result: " + result.ToString());
3
James Madison

Manchmal möchten Sie möglicherweise eine Schnittstelle verwenden, möglicherweise, weil Sie die Auflistung während des Komponententests verspotten möchten. Weitere Informationen zum Hinzufügen einer eigenen Schnittstelle zu ReadonlyCollection mithilfe eines Adapters finden Sie in meinem Blogeintrag .

0
Nick Taylor