Коллекции в .NET (IEnumerable, IQueryable, ICollection, IList)

Практически каждый день мы используем те или иные коллекции. Чаще всего даже не задумываясь какие и почему используем. В статье рассмотрим ключевые механизмы работы с коллекциями в .NET. В качестве старта рассмотрим иерархию некоторых интерфейсов в .NET.

коллекции в .net

Большинство коллекций реализует интерфейс IEnumerable<T> явно или неявно. Так как тип Array не является Generic типом, то он наследует только IEnumerable, а не IEnumerable<T>. 

IEnumerable

public interface IEnumerable
{
    IEnumerator GetEnumerator();
}

Интерфейс IEnumerable указывает, что тип реализует GetEnumerator. Становится доступна конструкция foreach. С IEnumerable часто используются расширения из System.Linq. Generic интерфейс используется при возвращении из запросов (например к базе данных или к другим коллекциям). Запрос, который выбирает целые числа будет типа IEnumerable<int>.

IEnumerable подходит для перебора по коллекции. Вы не можете изменить (добавить или удалить) данные из IEnumerable. В случае запроса к базе данных на сервере запрос вернет все данные (без фильтрации).

ienumerable запрос к базе данных

IEnumerable<T>

public interface IEnumerable<out T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();
}

Из объявления интерфейса заметно, что тип, реализующий IEnumerable<T> должен также реализовать IEnumerable.

IQueryable

Всякий раз, когда мы сталкиваемся с большим количеством данных необходимо подумать, какую коллекцию или какой тип использовать для работы с ними. В отличии от IEnumerable – IQueryable предлагает высокую производительность в случае работы с большим объемом данных. IQueryable предварительно фильтрует данные по запросу а затем отправляет только отфильтрованные данные клиенту.

IQueryable фильтрует данные

Разница между IQueryable и IEnumerable

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

Когда что применять

IEnumerable

  1. IEnumerable может двигаться только вперед по коллекции, он не может идти назад
  2. Хорошо подходит для работы с данными в памяти (списки, массивы)
  3. Подходит для LINQ to Object и LINQ to XML
  4. Поддерживает отложенное выполнение
  5. Не поддерживает произвольные запросы
  6. Не поддерживает ленивую загрузку
  7. Методы расширения, работающие с IEnumerable принимают функциональные объекты

Код на C#

MyDataContext dc = new MyDataContext ();
IEnumerable<Employee> list = dc.Employees.Where(p => p.Name.StartsWith("S"));
list = list.Take<Employee>(10); 

Сгенерированный SQL

SELECT [t0].[EmpID], [t0].[EmpName], [t0].[Salary] FROM [Employee] AS [t0]
WHERE [t0].[EmpName] LIKE @p0

IQueryable

  1. IQueryable может двигаться только вперед по коллекции, он не может идти назад
  2. IQueryable лучше работает с запросами к базе данных (вне памяти)
  3. Подходит для LINQ to SQL
  4. Поддерживает отложенное выполнение
  5. Поддерживает произвольные запросы (используя CreateQuery и метод Execute)
  6. Поддерживает ленивую загрузку
  7. Методы расширения, работающие с IQueryable принимают объекты выражения (expression tree)

Код на C#

MyDataContext dc = new MyDataContext ();
IQueryable<Employee> list = dc.Employees.Where(p => p.Name.StartsWith("S"));
list = list.Take<Employee>(10); 

Сгенерированный SQL

SELECT TOP 10 [t0].[EmpID], [t0].[EmpName], [t0].[Salary] FROM [Employee] AS [t0]
WHERE [t0].[EmpName] LIKE @p0

ICollection

Аналогично с IEnumerable существует 2 версии этого интерфейса. ICollection и ICollection<T>

public interface ICollection : IEnumerable
{
    int Count { get; }  
    bool IsSynchronized { get; }
    Object SyncRoot { get; }
 
    void CopyTo(Array array, int index);
}

ICollection наследуется от IEnumerable. Это означает, что дополнительно необходимо реализовать интерфейс IEnumerable. Интерфейс определяет размер, перечисления и методы синхронизации для всех не generic коллекций.

ICollection<T>

В отличии от IEnumerable и IEnumerable<T> – ICollection<T> отличается от своего не generic эквивалента.

public interface ICollection<T> : IEnumerable<T>, IEnumerable
{
    int Count { get; }
    bool IsReadOnly { get; }
 
    void Add(T item);
    void Clear();
    bool Contains(T item);
    void CopyTo(T[] array, int arrayIndex);
    bool Remove(T item);
}

Определяет методы для манипулирования generic коллекциями. По факту мы имеем методы добавления, удаления и очистки коллекции. Поэтому синхронизация тоже отличается. Впервые ICollection была опубликована в .NET 1.1.

IList

Как и все, что мы рассмотрели ранее IList существует в обычной и generic версии. Рассмотрим не generic версию интерфейса IList.

public interface IList : ICollection, IEnumerable
{
    bool IsFixedSize { get; }
    bool IsReadOnly { get; }
    Object this[int index] { get; set; }
 
    int Add(Object value);
    void Clear();
    bool Contains(Object value);
    int IndexOf(Object value);
    void Insert(int index, Object value);
    void Remove(Object value);
    void RemoveAt(int index);
}

В дополнении к интерфейса ICollection и IEnumerable, IList предоставляет методы для добавления и удаления элементов из коллекции. Он также позволяет узнать индекс элемента внутри коллекции. Также IList реализует индексатор, чтобы получить доступ к объектам через квадратные скобки. Например так:

var obj = list[index];

IList<T>

Generic версия отличается от своего собрата. А именно:

public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable
{
    T this[int index] { get; set; }
 
    int IndexOf(T item);
    void Insert(int index, T item);
    void RemoveAt(int index);
}

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

Заключение

Теперь, рассмотрев все интерфейсы мы можем решить, какой именно следует применять в конкретной ситуации. Как правильно, это хорошая идея зависеть только от тех вещей, которые реально нужны. 

Используя IEnumerable вместо IList мы защищаемся от незапланированных изменений в коллекции. Используя IEnumerable ваш метод может использовать любой тип, реализующий IEnumerable (из рисунка в начале статьи это любая коллекция). Код программы может легко измениться в будущем ничего не сломав, заменив IEnumerable на более сильный интерфейс.

За рамками данной статьи оказались другие интересные коллекции .NET, например очередь (Queue), стек (Stack), хеш таблица (HashTable) и словарь (Dictionary).

 

Комментарии

comments powered by Disqus