Я слушал эпизод Coding Blocks (отличный подкаст для программистов, я бы рекомендовал проверить их на https://www.codingblocks.net/), и они говорили о реализации интерфейсов и, в частности, о том, насколько это сложно. заключается в реализации IList<T>. Все, что они действительно хотели, - это создать свой собственный список, в который они могли бы добавлять и удалять элементы, но IList<T> требовалось гораздо больше методов, и это было излишним для того, что они хотели.

Общая цель этого обсуждения заключалась в том, чтобы сделать интерфейсы простыми и реализовать несколько интерфейсов, а не создавать множество больших интерфейсов. Это отличный совет, который мне много раз помогал. Однако они не упомянули, что, вероятно, между IEnumerable<T> и IList<T> существовал интерфейс, который удовлетворял бы их потребности. Это ICollection<T>, и я использую его все время. В этой статье я собираюсь исследовать разницу между этими тремя и их назначение.

Список ‹T›

Я подумал, что начну с List<T>. Большинство людей будут использовать List<T> во многих своих методах в качестве параметров. В документации .NET подпись List ‹T› следующая:

public class List<T> : System.Collections.Generic.ICollection<T>, System.Collections.Generic.IEnumerable<T>, System.Collections.Generic.IList<T>, System.Collections.Generic.IReadOnlyCollection<T>, System.Collections.Generic.IReadOnlyList<T>, System.Collections.IList

Реализовано много интерфейсов! Итак, что они все делают? И можем ли мы использовать некоторые из них вместо List<T>, чтобы сделать наши методы более гибкими?

IEnumerable ‹T›

Самый простой из реализованных интерфейсов. Посмотрим, как выглядит IEnumerable<T>:

IEnumerator<T> GetEnumerator(); //Returns an enumerator that iterates through the collection.

Один метод, GetEnumerator(). Все, что позволяет нам делать IEnumerable<T>, - это перечислять по нашему перечислимому. Для чего-то такого простого это дает много функциональных возможностей. Для начала, он дает нам целый ряд методов расширения, по сути, всю библиотеку LINQ.

Шаблон Enumerable и, в частности, IEnumerator был заявлен в книге Design Patterns (Gamma et al) как шаблон Iterator. Он определяет очень простой способ перебора коллекции. IEnumerator имеет только три метода

T Current() //Gets the element in the collection at the current position of the enumerator.
void MoveNext() //Advances the enumerator to the next element of the collection.
void Reset() //Sets the enumerator to its initial position, which is before the first element in the collection.

Эти три простых метода позволяют нам просматривать всю коллекцию, получая каждый элемент. Однако коллекция также может быть бесконечной, поскольку в интерфейсе IEnumerable<T> нет ничего, что заставляло бы коллекцию иметь начало и конец. Это важно помнить, потому что в зависимости от того, какое перечисление вы повторяете, вы потенциально можете создать бесконечный цикл, если не знаете, может ли ваш источник быть бесконечным или нет.

ICollection ‹T›

Так что IEnumerable<T> - это часто все, что вам нужно. Однако для ребят из блоков кодирования он хотел, чтобы он тоже мог добавлять элементы. IEnumerable<T> не предоставляет эти методы, но ICollection<T> предоставляет.

int Count { get; } //Gets the number of elements contained in the ICollection<T>.
bool IsReadOnly { get; } //Gets a value indicating whether the ICollection<T> is read-only.
void Add(T) //Adds an item to the ICollection<T>.
void Clear() //Removes all items from the ICollection<T>.
bool Contains(T) //Determines whether the ICollection<T> contains a specific value.
void CopyTo(T[], int) //Copies the elements of the ICollection<T> to an Array, starting at a particular Array index.
void Remove(T) //Removes the first occurrence of a specific object from the ICollection<T>.

Вот здесь все становится полезным, если вам нужна конечная коллекция, в которую можно добавлять элементы. Свойство Count требует, чтобы наша коллекция имела конечный размер. У нас есть наш Add(T) метод для добавления элементов в нашу коллекцию, а также еще несколько методов.

Это легко реализовать и, вероятно, удовлетворило бы потребности ребят из подкаста без необходимости реализовывать весь IList<T>. Я использую ICollection<T> все время. Обычно это самый простой интерфейс, который удовлетворяет мои потребности при работе с коллекцией, и вместо того, чтобы требовать весь IList<T> интерфейс, мы делаем наши методы более универсальными, требуя только более простого ICollection<T>.

IList ‹T›

Построенный на основе ICollection<T>, IList<T> добавляет немного больше функциональности, и в этом заключается идея порядка в нашей коллекции.

T Item[int] //Gets or sets the element at the specified index.
int IndexOf(T) //Determines the index of a specific item in the IList<T>.
int Insert(int, T) //Inserts an item to the IList<T> at the specified index.
void RemoveAt(int) //Removes the IList<T> item at the specified index.

Поэтому, если нам нужен порядок в нашей коллекции, именно тогда мы должны перейти от ICollection<T> и использовать вместо этого IList<T>. Но в большинстве случаев мы не используем индексы и не требуем гарантированного порядка, поэтому обычно это перебор.

IReadOnlyCollection ‹T› и IReadOnlyList ‹T›

Небольшое замечание по двум вышеперечисленным. IReadOnlyCollection<T> - один из моих любимых интерфейсов в сочетании с репозиториями. На самом деле вам не следует добавлять элементы непосредственно в коллекцию, которую вы возвращаете из репозитория (это может привести к неожиданным побочным эффектам), поэтому возврат версии вашей коллекции только для чтения гарантирует, что это невозможно.

Последние мысли

Так что, если вы извлечете что-то из этой статьи, я надеюсь, что это желание использовать самый простой интерфейс, который реализует то, что вам нужно. IEnumerable<T> прост и предоставляет множество функций для запросов и итераций по коллекциям, но сопряжен с множеством потенциальных рисков (например, опасности IQueryable<T>, но это уже другая статья). ICollection<T> - мой фаворит в группе, и, если мне не нужен гарантированный заказ, большую часть времени он удовлетворяет мои потребности. Наконец, если вам нужно, чтобы ваша коллекция располагалась в определенном порядке, предпочтительным интерфейсом должен быть IList<T>. Однако, если вы собираетесь делать только что-то одно, сделайте это, используя один из интерфейсов выше, а не конкретное определение List<T>, чтобы сделать ваши методы более универсальными!