Сравнение коллекций в .NET
В этой статье мы сравним коллекции в .NET (IEnumerable, IQueryable, ICollection, IList) и расмотрим в чем разница между IQueryable и IEnumerable и другими.
Как мы видим на схеме, коллекции реализуют интерфейс IEnumerable<T>
явно или неявно. Так как тип Array
не является Generic типом, то он наследует только IEnumerable, а не IEnumerable<T>.
IEnumerable
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
Интерфейс IEnumerable указывает, что тип реализует GetEnumerator
. Благодаря чему для него доступна конструкция foreach. С IEnumerable
часто используются расширения из System.Linq. Generic интерфейс используется при возвращении из запросов (например к базе данных или к другим коллекциям).
IEnumerable подходит для перебора по коллекции и отображения результатов на фронтенде. Вы не можете изменить (добавить или удалить) данные из IEnumerable. В случае запроса к базе данных на сервере запрос вернет все данные (без фильтров) как это показано в абстрактной картинке ниже.
IEnumerable<T>
public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
Из объявления интерфейса заметно, что тип, реализующий IEnumerable<T> должен также реализовать IEnumerable.
IQueryable
Всякий раз, когда мы сталкиваемся с большим количеством данных необходимо подумать, какую коллекцию или какой тип использовать для работы с ними. В отличии от IEnumerable
– IQueryable
предлагает высокую производительность в случае работы с большим объемом данных. IQueryable
предварительно фильтрует данные по запросу а затем отправляет только отфильтрованные данные клиенту.
Разница между IQueryable и IEnumerable
Основное отличие между этими интерфейсами в том, что IEnumerable
работает со всем массивом данных, а IQueryable
с отфильтрованным. IEnumerable
получает все данные на стороне сервера и загружает их в память а затем позволяет сделать фильтрацию по данным из памяти. Когда делается запрос к базе данных, IQueryable
выполняет запрос на серверной стороне и в запросе применяет фильтрацию.
Вот отличные картинки для сравнения этих 2х методов обращения к базе
Когда что использовать?
IEnumerable
- IEnumerable может двигаться только вперед по коллекции, он не может идти назад
- Хорошо подходит для работы с данными в памяти (списки, массивы)
- Подходит для LINQ to Object и LINQ to XML
- Поддерживает отложенное выполнение (Lazy Evaluation)
- Не поддерживает произвольные запросы
- Не поддерживает ленивую загрузку (lazy loading)
- Методы расширения, работающие с 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
- IQueryable может двигаться только вперед по коллекции, он не может идти назад
- IQueryable лучше работает с запросами к базе данных (вне памяти)
- Подходит для LINQ to SQL
- Поддерживает отложенное выполнение (Lazy Evaluation)
- Поддерживает произвольные запросы (используя CreateQuery и метод Execute)
- Поддерживает ленивую загрузку (lazy loading)
- Методы расширения, работающие с 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
и 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 на более сильный интерфейс.
- IEnumerable — единственное что нужно это пройти по всем элементам коллекции. Read-only доступ к коллекции
- ICollection — возможность изменять коллекцию и узнать ее размер
- IList — возможность изменение коллекции. В дополнении становится доступен порядок (индекс элементов)
- List — в соответствии с одним из принципов SOLID (Dependency inversion) - следует всегда зависеть от абстракций, нежели их реализаций.
За рамками данной статьи оказались другие интересные коллекции .NET, например очередь (Queue), стек (Stack), хеш таблица (HashTable) и словарь (Dictionary). О них вы можете почитать в другой нашей статье