0 12.7K ru

Сравнение коллекций в .NET

Categories: 💻 Programming

В этой статье мы сравним коллекции в .NET (IEnumerable, IQueryable, ICollection, IList)  и расмотрим в чем разница между IQueryable  и IEnumerable и другими.

Collection .net  flowchart

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

IEnumerable

public interface IEnumerable
{
    IEnumerator GetEnumerator();
}

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

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

ienumerable logic image

IEnumerable<T>

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

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

IQueryable

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

iqueryable flowchart db

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

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

Вот отличные картинки для сравнения этих 2х методов обращения к базе

ienumerable schemaIQueryable schema

 

Когда что использовать?

IEnumerable

  1. IEnumerable может двигаться только вперед по коллекции, он не может идти назад
  2. Хорошо подходит для работы с данными в памяти (списки, массивы)
  3. Подходит для LINQ to Object и LINQ to XML
  4. Поддерживает отложенное выполнение (Lazy Evaluation)
  5. Не поддерживает произвольные запросы
  6. Не поддерживает ленивую загрузку (lazy loading)
  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. Поддерживает отложенное выполнение (Lazy Evaluation)
  5. Поддерживает произвольные запросы (используя CreateQuery и метод Execute)
  6. Поддерживает ленивую загрузку (lazy loading)
  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 коллекциями. По факту мы имеем методы добавления, удаления и очистки коллекции. Поэтому синхронизация тоже отличается.

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 и IEnumerableIList предоставляет методы для добавления и удаления элементов из коллекции. Он также позволяет узнать индекс элемента внутри коллекции. Также 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 на более сильный интерфейс.

  • IEnumerable — единственное что нужно это пройти по всем элементам коллекции. Read-only доступ к коллекции
  • ICollection — возможность изменять коллекцию и узнать ее размер
  • IList — возможность изменение коллекции. В дополнении становится доступен порядок (индекс элементов)
  • List — в соответствии с одним из принципов SOLID (Dependency inversion) - следует всегда зависеть от абстракций, нежели их реализаций.

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

Источник

Comments:

Please log in to be able add comments.