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
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;
}
}
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.
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.
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());
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 .