Collection <T> a List <T>, czego należy używać w swoich interfejsach?

162

Kod wygląda jak poniżej:

namespace Test
{
    public interface IMyClass
    {
        List<IMyClass> GetList();
    }

    public class MyClass : IMyClass
    {
        public List<IMyClass> GetList()
        {
            return new List<IMyClass>();
        }
    }
}

Po uruchomieniu analizy kodu otrzymuję następujące zalecenie.

Ostrzeżenie 3 CA1002: Microsoft.Design: Zmień 'List' w 'IMyClass.GetList ()', aby użyć Collection, ReadOnlyCollection lub KeyedCollection

Jak mam to naprawić i jakie są tutaj dobre praktyki?

bovium
źródło

Odpowiedzi:

234

Aby odpowiedzieć na pytanie „dlaczego”, dlaczego nie List<T> , powody są przyszłościowe i prostota API.

Gotowość na przyszłość

List<T>nie jest zaprojektowany tak, aby można go było łatwo rozszerzać poprzez tworzenie podklas; został zaprojektowany tak, aby był szybki we wdrożeniach wewnętrznych. Zauważysz, że metody na nim nie są wirtualne i dlatego nie można ich zastąpić, i nie ma żadnych punktów zaczepienia w jego operacjach Add/ Insert/ Remove.

Oznacza to, że jeśli będziesz musiał zmienić zachowanie kolekcji w przyszłości (np. Aby odrzucić obiekty puste, które ludzie próbują dodać, lub wykonać dodatkową pracę, gdy tak się stanie, np. Zaktualizować stan twojej klasy), musisz zmienić typ kolekcji, wrócisz do takiej, którą możesz podklasować, co będzie zepsutą zmianą interfejsu (oczywiście zmiana semantyki rzeczy, takich jak niedopuszczanie do wartości null, może być również zmianą interfejsu, ale rzeczy takie jak aktualizacja wewnętrznego stanu klasy nie byłyby).

Dlatego zwracając klasę, którą można łatwo podzielić na podklasę, taką jak Collection<T>lub interfejs, taki jak IList<T>, ICollection<T>lub IEnumerable<T>możesz zmienić wewnętrzną implementację, aby była innym typem kolekcji, aby spełnić Twoje potrzeby, bez łamania kodu konsumentów, ponieważ nadal można ją zwrócić jako rodzaj, jakiego oczekują.

Prostota interfejsu API

List<T>zawiera wiele przydatnych funkcji, takich jak BinarySearch, Sorti tak dalej. Jednak jeśli jest to kolekcja, którą ujawniasz, prawdopodobnie kontrolujesz semantykę listy, a nie konsumentów. Więc chociaż twoja klasa wewnętrznie może potrzebować tych operacji, jest bardzo mało prawdopodobne, że konsumenci twojej klasy będą chcieli (lub nawet powinni) je wywoływać.

W związku z tym, oferując prostszą klasę kolekcji lub interfejs, zmniejszasz liczbę członków, których widzą użytkownicy Twojego interfejsu API, i ułatwiasz im korzystanie.

Greg Beech
źródło
1
Ta odpowiedź jest martwa. Kolejną dobrą lekturę na ten temat można znaleźć tutaj: blogs.msdn.com/fxcop/archive/2006/04/27/…
senfo
7
Widzę twoje pierwsze uwagi, ale nie wiem, czy zgadzam się z twoją częścią dotyczącą prostoty API.
Boris Callens,
stackoverflow.com/a/398988/2632991 Tutaj też jest naprawdę dobry post o tym, jaka jest różnica między kolekcjami a listami.
El Mac,
2
blogs.msdn.com/fxcop/archive/2006/04/27/… -> Nie żyje. Czy mamy do tego nowsze informacje?
Machado
50

Osobiście zadeklarowałbym, że zwraca interfejs, a nie konkretną kolekcję. Jeśli naprawdę chcesz mieć dostęp do listy, użyj IList<T>. W przeciwnym razie rozważ ICollection<T>i IEnumerable<T>.

Jon Skeet
źródło
1
Czy IList rozszerzyłby wówczas interfejs ICollection?
bovium
8
@Jon: Wiem, że to stare, ale czy możesz skomentować to, co mówi Krzysztof na blogs.msdn.com/b/kcwalina/archive/2005/09/26/474010.aspx ? W szczególności jego komentarz, We recommend using Collection<T>, ReadOnlyCollection<T>, or KeyedCollection<TKey,TItem> for outputs and properties and interfaces IEnumerable<T>, ICollection<T>, IList<T> for inputs. CA1002 wydaje się zgadzać z komentarzami Krzysztofa. Nie mogę sobie wyobrazić, dlaczego konkretna kolekcja byłaby zalecana zamiast interfejsu i dlaczego rozróżnienie między danymi wejściowymi / wyjściowymi.
Nelson Rothermel
3
@Nelson: Rzadko się zdarza, aby wymagać od rozmówców przekazywania niezmiennej listy, ale rozsądne jest zwrócenie takiej, aby wiedzieli, że jest ona zdecydowanie niezmienna. Nie jestem jednak pewien co do innych kolekcji. Byłoby miło mieć więcej szczegółów.
Jon Skeet
2
To nie jest dla konkretnego przypadku. Oczywiście generalnie ReadOnlyCollection<T>nie miałoby to sensu w przypadku wkładu. Podobnie, IList<T>jak mówi wejście, „Potrzebuję Sort () lub innego elementu członkowskiego, który ma IList”, co nie ma sensu dla danych wyjściowych. Chodziło mi jednak o to, dlaczego miałbym ICollection<T>być zalecany jako wkład i Collection<T>jako wyjście. Dlaczego nie użyć ICollection<T>jako wyjścia również tak, jak sugerowałeś?
Nelson Rothermel
9
Myślę, że ma to związek z niejednoznacznością. Collection<T>i ReadOnlyCollection<T>oba pochodzą z ICollection<T>(tj. nie ma IReadOnlyCollection<T>). Jeśli zwrócisz interfejs, nie jest oczywiste, który to jest i czy można go zmodyfikować. W każdym razie dziękuję za Twój wkład. To był dla mnie dobry test poczytalności.
Nelson Rothermel
3

Chyba nikt jeszcze nie odpowiedział na część „dlaczego” ... więc proszę bardzo. Powodem, dla którego "powinieneś" używać a Collection<T>zamiast a, List<T>jest to, że jeśli ujawnisz a List<T>, to każdy, kto uzyska dostęp do twojego obiektu, będzie mógł modyfikować elementy na liście. Natomiast Collection<T>ma wskazywać, że tworzysz własne metody „Dodaj”, „Usuń” itp.

Prawdopodobnie nie musisz się tym martwić, ponieważ prawdopodobnie kodujesz interfejs tylko dla siebie (lub może kilku kolegów). Oto kolejny przykład, który może mieć sens.

Jeśli masz publiczną tablicę, np:

public int[] MyIntegers { get; }

Można by pomyśleć, że ponieważ jest tylko akcesor „get”, którego nikt nie może zepsuć wartości, ale to nieprawda. Każdy może zmienić wartości w środku, tak jak to:

someObject.MyIngegers[3] = 12345;

Osobiście po prostu używałbym List<T>w większości przypadków. Ale jeśli projektujesz bibliotekę klas, którą zamierzasz rozdać przypadkowym programistom i musisz polegać na stanie obiektów ... wtedy będziesz chciał stworzyć własną kolekcję i zablokować ją stamtąd: )

Timothy Khouri
źródło
„Jeśli zwrócisz List <T> do kodu klienta, nigdy nie będziesz w stanie otrzymywać powiadomień, gdy kod klienta modyfikuje kolekcję.” - FX COP ... Zobacz także " msdn.microsoft.com/en-us/library/0fss9skc.aspx " ... wow, wydaje mi się, że nie jestem poza bazą :)
Timothy Khouri
Wow - po latach widzę, że link, który wkleiłem w powyższym komentarzu, miał na końcu cytat, więc nie zadziałał ... msdn.microsoft.com/en-us/library/0fss9skc.aspx
Timothy Khouri,
2

Chodzi głównie o wyabstrahowanie własnych implementacji zamiast ujawniania obiektu List do bezpośredniej manipulacji.

Nie jest dobrą praktyką pozwalanie innym obiektom (lub ludziom) bezpośrednio modyfikować stan twoich obiektów. Pomyśl o metodach pobierających / ustawiających właściwości.

Kolekcja -> Dla zwykłej kolekcji
ReadOnlyCollection -> Dla kolekcji, które nie powinny być modyfikowane
KeyedCollection -> Gdy chcesz zamiast tego słowniki.

Sposób rozwiązania tego problemu zależy od tego, co ma robić Twoja klasa i od celu metody GetList (). Czy możesz rozwinąć?

chakrit
źródło
1
Ale Collection <T> i KeyedCollection <,> też nie są tylko do odczytu.
nawfal
@nawfal A gdzie ja powiedziałem, że to jest?
chakrit
1
Oświadczasz, że nie pozwalasz innym obiektom (lub ludziom) bezpośrednio modyfikować stanu twoich obiektów i podajesz 3 typy kolekcji. O ReadOnlyCollectionile pozostałe dwa nie są mu posłuszne.
nawfal,
To odnosi się do „dobrych praktyk”. Proszę o odpowiedni kontekst. Poniższa lista przedstawia po prostu podstawowe wymagania dla takich typów, ponieważ PO chce zrozumieć ostrzeżenie. Następnie pytam go o cel, GetList()aby móc lepiej mu pomóc.
chakrit
1
Ok, dobrze, rozumiem tę część. Jednak według mnie rozumowanie nie jest rozsądne. Mówisz, że Collection<T>pomaga w abstrakcji wewnętrznej implementacji i zapobiega bezpośredniej manipulacji wewnętrzną listą. W jaki sposób? Collection<T>jest tylko opakowaniem działającym na tej samej przekazanej instancji. To dynamiczna kolekcja przeznaczona do dziedziczenia, nic więcej (odpowiedź Grega jest tutaj bardziej odpowiednia).
nawfal
1

W tego rodzaju przypadkach zwykle staram się ujawnić jak najmniejszą ilość implementacji, jaka jest potrzebna. Jeśli konsumenci nie muszą wiedzieć, że faktycznie używasz listy, nie musisz jej zwracać. Wracając zgodnie z sugestią firmy Microsoft dotyczącą kolekcji, ukrywasz fakt, że używasz listy przed użytkownikami swojej klasy i izolujesz ich przed wewnętrzną zmianą.

Harald Scheirich
źródło
1

Coś do dodania, chociaż minęło dużo czasu, odkąd o to zapytano.

Gdy typ listy pochodzi od List<T>zamiast Collection<T>, nie można zaimplementować chronionych metod wirtualnych, które Collection<T>implementują. Oznacza to, że typ pochodny nie może odpowiedzieć w przypadku wprowadzenia jakichkolwiek modyfikacji na liście. Dzieje się tak, ponieważ List<T>zakłada się, że wiesz, kiedy dodajesz lub usuwasz elementy. Możliwość odpowiadania na powiadomienia to narzut i dlatego List<T>go nie oferuje.

W przypadkach, gdy kod zewnętrzny ma dostęp do Twojej kolekcji, możesz nie mieć kontroli nad dodawaniem lub usuwaniem elementu. Dlatego Collection<T>zapewnia sposób sprawdzenia, kiedy lista została zmodyfikowana.

NullReference
źródło
0

Nie widzę problemu ze zwrotem czegoś takiego

this.InternalData.Filter(crteria).ToList();

Jeśli zwróciłem odłączoną kopię danych wewnętrznych, albo odpisałem wynik zapytania o dane - mogę bezpiecznie wrócić List<TItem>bez ujawniania szczegółów implementacji i pozwolić na wygodne wykorzystanie zwróconych danych.

Ale to zależy od tego, jakiego rodzaju konsumenta oczekuję - jeśli jest to coś w rodzaju siatki danych, wolę zwrócić, IEnumerable<TItem> która i tak będzie skopiowaną listą pozycji w większości przypadków :)

Konstantin Isaev
źródło
-1

Cóż, klasa Collection jest po prostu klasą opakowującą wokół innych kolekcji, aby ukryć szczegóły ich implementacji i inne funkcje. Myślę, że ma to coś wspólnego z właściwościami ukrywającymi wzorzec kodowania w językach zorientowanych obiektowo.

Myślę, że nie powinieneś się tym martwić, ale jeśli naprawdę chcesz zadowolić narzędzie do analizy kodu, wykonaj następujące czynności:

//using System.Collections.ObjectModel;

Collection<MyClass> myCollection = new Collection<MyClass>(myList);
Tamas Czinege
źródło
1
Przepraszam, to była literówka. I menat Collection <MyClass>. Naprawdę nie mogę się doczekać, aby dostać w swoje ręce C # 4 i ogólną kowariancję przy okazji!
Tamas Czinege
Collection<T>O ile rozumiem, opakowanie w a nie chroni ani siebie, ani podstawowej kolekcji.
nawfal
Ten fragment kodu miał „zadowolić narzędzie do analizy kodu”. Nie sądzę, by @TamasCzinege nigdzie powiedział, że używanie Collection<T>natychmiast ochroni twoją podstawową kolekcję.
chakrit