Udostępnij przez


Wskazówki dotyczące kolekcji

Uwaga / Notatka

Ta treść jest przedrukowana za zgodą Pearson Education, Inc. z Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries, 2. wydanie. Wydanie to zostało opublikowane w 2008 roku, a książka została w pełni zmieniona w trzecim wydaniu. Niektóre informacje na tej stronie mogą być nieaktualne.

Każdy typ zaprojektowany specjalnie do manipulowania grupą obiektów o niektórych typowych cechach można uznać za kolekcję. Prawie zawsze jest właściwe, aby takie typy implementowały IEnumerable lub IEnumerable<T>, więc w tej sekcji rozważamy tylko typy implementujące jeden lub oba te interfejsy jako kolekcje.

❌ NIE używaj słabo typiowanych kolekcji w publicznych interfejsach API.

Typ wszystkich zwracanych wartości i parametrów reprezentujących elementy kolekcji powinien być dokładnym typem elementu, a nie żadnym z jego typów podstawowych (dotyczy to tylko publicznych elementów członkowskich kolekcji).

❌ NIE używaj ArrayList ani List<T> w publicznych interfejsach API.

Te typy to struktury danych przeznaczone do użycia w implementacji wewnętrznej, a nie w publicznych interfejsach API. List<T> jest zoptymalizowany pod kątem wydajności i efektywności energetycznej, kosztem przejrzystości interfejsów API i elastyczności. Jeśli na przykład zwrócisz wartość List<T>, nigdy nie będzie można otrzymywać powiadomień, gdy kod klienta modyfikuje kolekcję. Ponadto List<T> uwidacznia wielu członków, takich jak BinarySearch, którzy nie są przydatni lub zastosowalni w niektórych scenariuszach. W poniższych dwóch sekcjach opisano typy (abstrakcji) przeznaczone specjalnie do użytku w publicznych interfejsach API.

❌ NIE używaj Hashtable ani Dictionary<TKey,TValue> w publicznych interfejsach API.

Te typy to struktury danych przeznaczone do użycia w implementacji wewnętrznej. Publiczne API powinny używać typu IDictionary, IDictionary <TKey, TValue> lub niestandardowego typu implementującego jeden lub oba interfejsy.

❌ NIE używaj IEnumerator<T>, IEnumerator ani żadnego innego typu, który implementuje jeden z tych interfejsów, z wyjątkiem przypadku, gdy jest to typ zwracany przez metodę GetEnumerator.

Typy zwracające wyliczenia z metod innych niż GetEnumerator nie mogą być używane z instrukcją foreach .

❌ NIE implementuj zarówno IEnumerator<T> jak i IEnumerable<T> na tym samym typie. To samo dotyczy niegenerycznych interfejsów IEnumerator i IEnumerable.

Parametry kolekcji

✔️ Należy użyć najmniej wyspecjalizowanego typu, który jest możliwy jako typ parametru. Większość członków przyjmujących kolekcje jako parametry używa interfejsu IEnumerable<T> .

❌ UNIKAJ używania ICollection<T> lub ICollection jako parametru tylko w celu uzyskania dostępu do właściwości Count.

Zamiast tego rozważ użycie metody IEnumerable<T> lub IEnumerable i dynamiczne sprawdzanie, czy obiekt implementuje ICollection<T> , czy ICollection.

Właściwości kolekcji i wartości zwracane

❌ NIE należy definiować konfigurowalnych właściwości kolekcji.

Użytkownicy mogą zastąpić zawartość kolekcji, najpierw ją czyszcząc, a następnie dodając nową zawartość. Jeśli zastąpienie całej kolekcji jest typowym scenariuszem, rozważ udostępnienie metody AddRange w kolekcji.

✔️ Używaj Collection<T> lub podklasy Collection<T> dla właściwości lub wartości zwracanej reprezentującej kolekcję odczytu/zapisu.

Jeśli Collection<T> nie spełnia pewnych wymagań (np. kolekcja nie może implementować IList), należy użyć kolekcji niestandardowej poprzez zaimplementowanie IEnumerable<T>, ICollection<T> lub IList<T>.

✔️ Używaj ReadOnlyCollection<T>, podklasy ReadOnlyCollection<T>, lub IEnumerable<T> w rzadkich przypadkach dla właściwości lub zwracanych wartości reprezentujących kolekcje tylko do odczytu.

Ogólnie rzecz biorąc, preferuj wartość ReadOnlyCollection<T>. Jeśli nie spełnia wymagań (np. kolekcja nie może implementować IList), użyj kolekcji niestandardowej, implementując IEnumerable<T>ICollection<T>, lub IList<T>. Jeśli zaimplementujesz niestandardową kolekcję w trybie tylko do odczytu, zaimplementuj ICollection<T>.IsReadOnly, aby zwrócić true.

W przypadkach, gdy masz pewność, że jedynym scenariuszem, który kiedykolwiek chcesz obsługiwać, jest iteracja tylko do przodu, możesz po prostu użyć polecenia IEnumerable<T>.

✔️ ROZWAŻ użycie podklas ogólnych kolekcji bazowych zamiast bezpośredniego używania kolekcji.

Dzięki temu można uzyskać lepszą nazwę i dodać członków wspomagających, które nie są obecne w podstawowych typach kolekcji. Dotyczy to szczególnie interfejsów API wysokiego poziomu.

✔️ ROZWAŻ zwrócenie podklasy Collection<T> lub ReadOnlyCollection<T> z bardzo często używanych metod i właściwości.

Umożliwi to dodanie metod pomocnika lub zmianę implementacji kolekcji w przyszłości.

✔️ ROZWAŻ użycie kolekcji kluczy, jeśli elementy przechowywane w kolekcji mają unikatowe klucze (nazwy, identyfikatory itp.). Kolekcje kluczowe to zbiory, które mogą być indeksowane zarówno za pomocą liczby całkowitej, jak i klucza, i są zazwyczaj implementowane poprzez dziedziczenie z KeyedCollection<TKey,TItem>.

Kolekcje kluczy zwykle mają większe zużycie pamięci i nie powinny być używane, jeśli obciążenie pamięci przewyższa korzyści wynikające z posiadania kluczy.

❌ NIE zwracaj wartości null z właściwości kolekcji ani z metod zwracających kolekcje. Zamiast tego zwróć pustą kolekcję lub pustą tablicę.

Ogólną regułą jest to, że kolekcje lub tablice o wartości null i puste (0 elementów) powinny być traktowane tak samo.

Migawki kontra kolekcje na żywo

Kolekcje reprezentujące stan w pewnym momencie są nazywane kolekcjami migawkowymi. Na przykład kolekcja zawierająca wiersze zwrócone z zapytania bazy danych stanowi migawkę. Kolekcje, które zawsze reprezentują bieżący stan, są nazywane kolekcjami na żywo. Na przykład kolekcja ComboBox elementów jest dynamiczną kolekcją.

❌ NIE zwracaj kolekcji zrzutów danych z właściwości. Właściwości powinny zwracać kolekcje na żywo.

Metody pobierające właściwości powinny być bardzo lekkie. Zwracanie migawki wymaga utworzenia kopii kolekcji wewnętrznej w operacji O(n).

✔️ DO używać kolekcji migawek lub dynamicznej IEnumerable<T> (lub jej podtypu) do reprezentowania kolekcji, które są nietrwałe (tj. mogą ulec zmianie bez jawnego modyfikowania kolekcji).

Ogólnie rzecz biorąc, wszystkie kolekcje reprezentujące zasób udostępniony (np. pliki w katalogu) są nietrwałe. Takie kolekcje są bardzo trudne lub niemożliwe do zaimplementowania jako kolekcje na żywo, chyba że implementacja jest po prostu modułem wyliczającym tylko do przodu.

Wybieranie między tablicami i kolekcjami

✔️ Preferuj kolekcje zamiast tablic.

Kolekcje zapewniają większą kontrolę nad zawartością, mogą ewoluować wraz z upływem czasu i są bardziej użyteczne. Ponadto używanie tablic w scenariuszach tylko do odczytu jest zniechęcane, ponieważ koszt klonowania tablicy jest zbyt uciążliwy. Badania użyteczności wykazały, że niektórzy deweloperzy czują się bardziej komfortowo przy użyciu interfejsów API opartych na kolekcji.

Jeśli jednak opracowujesz interfejsy API niskiego poziomu, lepszym rozwiązaniem może być użycie tablic w scenariuszach odczytu i zapisu. Tablice mają mniejsze zużycie pamięci, co pomaga zmniejszyć rozmiar zestawu roboczego, a dostęp do elementów w tablicy jest szybszy, ponieważ jest zoptymalizowany przez środowisko uruchomieniowe.

✔️ ROZWAŻ użycie tablic w interfejsach API niskiego poziomu, aby zminimalizować zużycie pamięci i zmaksymalizować wydajność.

✔️ Należy używać tablic bajtów zamiast kolekcji bajtów.

❌ NIE należy używać tablic dla właściwości, jeśli właściwość musiałaby zwrócić nową tablicę (np. kopię tablicy wewnętrznej) za każdym razem, gdy wywoływany jest element pobierający właściwości.

Implementowanie kolekcji niestandardowych

✔️ Podczas projektowania nowych kolekcji należy rozważyć dziedziczenie z Collection<T>, ReadOnlyCollection<T> lub KeyedCollection<TKey,TItem>.

✔️ Implementuj IEnumerable<T> podczas projektowania nowych kolekcji. Rozważ wdrożenie ICollection<T> , a nawet IList<T> tam, gdzie ma sens.

Podczas implementowania takiej niestandardowej kolekcji należy postępować zgodnie ze wzorcem interfejsu API ustalonym przez Collection<T> i ReadOnlyCollection<T> możliwie najdokładniej. To znaczy, jawnie zaimplementuj tych samych członków, nazwij parametry, tak jak są nazwane w tych dwóch kolekcjach, i tak dalej.

✔️ ROZWAŻ zaimplementowanie niegenerycznych interfejsów kolekcji (IList i ICollection), jeśli kolekcja będzie często przekazywana do interfejsów API przyjmujących te interfejsy jako dane wejściowe.

❌ UNIKAJ implementowania interfejsów kolekcji na typach ze złożonymi interfejsami API niepowiązanymi z koncepcją kolekcji.

❌ NIE dziedziczyj z niegenerycznych kolekcji bazowych, takich jak CollectionBase. Zamiast tego użyj Collection<T>, ReadOnlyCollection<T>i KeyedCollection<TKey,TItem>.

Nazywanie kolekcji niestandardowych

Kolekcje (typy implementujące IEnumerable) są tworzone głównie z dwóch powodów: (1) w celu utworzenia nowej struktury danych z operacjami specyficznymi dla struktury i często różnych właściwości wydajności niż istniejące struktury danych (np List<T>. , LinkedList<T>, Stack<T>) i (2) w celu utworzenia wyspecjalizowanej kolekcji do przechowywania określonego zestawu elementów (np. StringCollection). Struktury danych są najczęściej używane w wewnętrznej implementacji aplikacji i bibliotek. Wyspecjalizowane kolekcje mają być uwidocznione głównie w interfejsach API (jako typy właściwości i parametrów).

✔️ Należy użyć sufiksu "Słownik" w nazwach abstrakcji implementujących IDictionary lub IDictionary<TKey,TValue>.

✔️ Należy używać sufiksu "Kolekcja" w nazwach typów, które implementują IEnumerable (lub któregoś z jego potomków) i reprezentują listę elementów.

✔️ Należy użyć odpowiedniej nazwy struktury danych dla niestandardowych struktur danych.

❌ UNIKAJ używania wszelkich sufiksów sugerujących konkretną implementację, na przykład "LinkedList" lub "Hashtable", w nazwach abstrakcji kolekcji.

✔️ ROZWAŻ prefiksowanie nazw kolekcji o nazwie typu elementu. Na przykład kolekcja przechowującą elementy typu Address (implementujące IEnumerable<Address>) powinna mieć nazwę AddressCollection. Jeśli typ elementu jest interfejsem, można pominąć prefiks "I" typu elementu. W ten sposób kolekcję elementów IDisposable można nazwać DisposableCollection.

✔️ ROZWAŻ użycie prefiksu "ReadOnly" w nazwach kolekcji tylko do odczytu, jeśli odpowiednia kolekcja zapisywalna może zostać dodana lub już istnieje w strukturze.

Na przykład kolekcja ciągów tylko do odczytu powinna być nazywana .ReadOnlyStringCollection

© Części 2005, 2009 Microsoft Corporation. Wszelkie prawa zastrzeżone.

Przedrukowane za zgodą Pearson Education, Inc. z Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition przez Krzysztofa Cwalinę i Brada Abramsa, opublikowane 22 października 2008 przez Addison-Wesley Professional w ramach serii Microsoft Windows Development.

Zobacz także