ReadOnlyCollection czy IEnumerable for exposing member collections?
Czy Jest jakiś powód, aby ujawnić wewnętrzną kolekcję jako ReadOnlyCollection, a nie IEnumerable, jeśli kod wywołujący tylko iteruje nad kolekcją?
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);
Jak widzę, że numerable jest podzbiorem interfejsu ReadOnlyCollection i nie pozwala użytkownikowi na modyfikację kolekcji. Więc jeśli interfejs IEnumberable jest wystarczający, to jest to jeden do użycia. To właściwy sposób rozumowania, czy coś przeoczyłem?
Thanks / Erik
5 answers
Bardziej nowoczesne rozwiązanie
Jeśli nie potrzebujesz kolekcji wewnętrznej, aby była zmienna, możesz użyć System.Collections.Immutable
package, Zmień typ pola na niezmienną kolekcję, a następnie ujawnij to bezpośrednio-zakładając oczywiście, że sama Foo
jest niezmienna.
Zaktualizowana odpowiedź na pytanie bardziej bezpośrednio
To zależy od tego, jak bardzo ufasz kodowi. Jeśli masz pełną kontrolę nad wszystkim, co kiedykolwiek zadzwoni do tego członka, a ty gwarantujesz , że żaden kod nigdy nie użyje:Czy istnieje jakiś powód, aby ujawnić zbiór wewnętrzny jako ReadOnlyCollection, a nie IEnumerable, jeśli wywołanie Kod tylko iteruje nad zbiorem?
ICollection<Foo> evil = (ICollection<Foo>) bar.Foos;
evil.Add(...);
Wtedy oczywiście, nic się nie stanie, jeśli po prostu zwrócisz kolekcję bezpośrednio. Generalnie staram się być trochę bardziej paranoiczny niż to.
Podobnie, jak mówisz: jeśli tylko potrzebujesz IEnumerable<T>
, to po co wiązać się z coś mocniejszego?
Oryginalna odpowiedź
Jeśli używasz. NET 3.5, możesz uniknąć kopiowania i, unikając prostego rzucania, używając prostego wywołania do pominięcia:
public IEnumerable<Foo> Foos {
get { return foos.Skip(0); }
}
(istnieje wiele innych opcji do owijania trywialnie-fajną rzeczą w Skip
over Select / Where jest to, że nie ma delegata do wykonywania bez sensu dla każdej iteracji.)
Jeśli nie używasz. NET 3.5 możesz napisać bardzo prosty wrapper, aby zrobić to samo rzecz:
public static IEnumerable<T> Wrapper<T>(IEnumerable<T> source)
{
foreach (T element in source)
{
yield return element;
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-17 17:26:33
Jeśli potrzebujesz tylko iteracji poprzez zbiór:
foreach (Foo f in bar.Foos)
W takim razie wystarczy zwrócenie liczby.
Jeśli potrzebujesz losowego dostępu do przedmiotów:
Foo f = bar.Foos[17];
Następnie owinąć go w ReadOnlyCollection .
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-29 12:32:53
Jeśli to zrobisz, to nic nie powstrzyma Twoich rozmówców, którzy wrzucą liczbę z powrotem do kolekcji, a następnie ją zmodyfikują. ReadOnlyCollection usuwa tę możliwość, chociaż nadal można uzyskać dostęp do bazowej kolekcji zapisywalnej za pomocą refleksji. Jeśli kolekcja jest mała, bezpiecznym i łatwym sposobem obejścia tego problemu jest zwrócenie kopii.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-29 12:28:06
Unikam używania ReadOnlyCollection tak bardzo, jak to możliwe, jest to w rzeczywistości znacznie wolniejsze niż używanie zwykłej listy. Zobacz ten przykład:
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());
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-10 05:38:21
Czasami możesz chcieć użyć interfejsu, być może dlatego, że chcesz wyśmiewać kolekcję podczas testów jednostkowych. Zobacz mój wpis na blogu aby dodać własny interfejs do ReadonlyCollection za pomocą adaptera.