Сравнение коллекций в .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). О них вы можете почитать в другой нашей статье