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

Author: Erik Öjebo, 2009-01-29

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

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?

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:
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;
    }
}
 83
Author: Jon Skeet,
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 .

 38
Author: Vojislav Stojkovic,
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.

 25
Author: Stu Mackellar,
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());
 3
Author: James Madison,
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.

 0
Author: ,
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-02-11 15:25:37