Пространство имен System.Collection.Generic содержит несколько интерфейсов для работы с универсальными коллекциями.
Наиболее часто мне попадаются следующие интерфейсы:
IEnumerable
ICollection
IList
Хотелось бы узнать, в каких случая рекомендуется использовать тот или иной тип, какие плюсы и минусы использования их по соотношению к друг другу.
Ответы
Ответ 1
Это семантически разные коллекции, каждая из них добавляет более строгие ограничения, но дает новые возможности:
IEnumerable - это, строго говоря, не коллекция. Это просто последовательность, которую можно перебрать по одному элементу. Нельзя, например, рассчитывать на то, что
ее можно перебрать дважды
ее вообще можно перебрать до конца
ICollection - это полноценная коллекция. У нее есть конечное количество элементов
элементы в нее можно добавлять. Имея элемент, можно проверить на существование его в коллекции. И, естественно, коллекцию можно очистить.
IList - это коллекция, элементы в которой пронумерованы.
Соответственно, применять нужно то, что позволяют ограничения в каждом конкретном случае. Например:
Последовательность Фибоначчи - это IEnumerable - ее можно перебрать. Но эт
не ICollection - в ней нельзя пересчитать элементы, и в нее нельзя добавить новый элемент. И ее точно нельзя "очистить" :)
Множество - HashSet - это ICollection, количество элементов в нем известно
можно добавить новый элемент, можно проверить на существование элемента. Но элементы в множестве никак не отсортированы и не пронумерованы. Соответственно, это не List. Аналогично - Dictionary - ICollection<>, но не IList<>
Решение "что использовать" обычно принимается в двух случаях:
При реализации собственной коллекции - и тогда стоит просто выбирать тот интерфейс, под ограничения которого ваша коллекция попадает.
При возврате значения их свойства или метода. Например, внутри у вас есть List
Возвращать ли его как IList, или как IEnumerable - опять же, зависит от контекста
Если подразумевается возврат именно коллекции, но ваш класс не должен реагировать н
ее модификацию - то можно вернуть IReadOnlyCollection. Если вызывающему достаточно последовательности - ограничьтесь IEnumerable. Возвращать внутренний List или T[], в зависимости от контекста, может быть небезопасно - вызывающий код сможет напрямую поменять внутренний список вашего класса и поломать вам всю инкапсуляцию.
Сама по себе тема подробно раскрыта в официальном Framework Design Guidelines - Guidelines for Collections.
Ответ 2
В большинстве случаев достаточно передать или вернуть IEnumerable, потому чт
обычно вызываемый или вызывающий код просто итерируется по коллекции. Об остальных типах коллекций стоит задумываться тогда, когда в этом реально есть необходимость.
Плюсом этого интерфейса является возможность вернуть бесконечные последовательности
Правда не всякий вызывающий код предполагает подобное, поэтому применение возможности не такое широкое.
Из минусов стоит отметить, что по этому интерфейсу не всегда ясно, что за ним скрывается
В результате, чтобы избежать двойной итерации (например, для проверки размера пере
итерацией), часто добавляют вызов ToList(), даже если за интерфесом список и скрывается, в результате в памяти возникает лишний список. (Эта проблема решается с помощью метода типа AsList(), который смотрит реализуемые интерфейсы.)
ICollection подразумевает коллекцию, то есть хранилище, в которое можно добавлят
и из которого можно удалять элементы. IList добавляет понятие индекса элемента. Оба имеют свойство Count, то есть необходимость в вызове ToList() пропадает.
И не стоит забывать про IReadOnlyCollection и IReadOnlyList. Если предполагается
что вызывающий код не будет изменять коллекции, то имеет смысл возвращать коллекции только для чтения.
Что точно не надо делать в публичных интерфейсах — это возвращать конкретные коллекции
особенно List или T[]. Это ограничит ваши возможности изменять реализацию без поломки обратной совместимости в будущем: список и массив не предполагают расширения, в отличие от базового класса Collection. (В старом коде .NET много где допущена эта ошибка.)
Если вы работаете с внутренней реализацией, а не с публичным интерфейсом, то обычн
нет смысла менять тип переменных, возвращаемых типов и типов аргументов на интерфейсы: вы потеряете время на преобразованиях, а оптимизации того же List станут недоступными при вызове методов через интерфейс.
Ответ 3
Для работы с коллекциями часто используют List -- если требуется чтобы у получателя коллекции была возможность изменения коллекции.
public IList GetList() {
return new List() { 1, 2, 3 };
}
var lst = GetList();
lst.Add(4);
Если уже есть коллекция, и ее надо вернуть как readonly:
private List _List = new List() {1, 2, 3};
public IList GetReadOnlyList() {
return _List.AsReadOnly(); // создает обертку
}
Если значения надо содавать по-запросу
public IEnumerable GetValues() {
var i = 0;
while (true)
yield return i++;
}
var values = GetValues().Take(10);
foreach(var v in values)
Console.WriteLine(v);
Если в коллекции находятся объекты, у которых меняются значения свойств, и надо сообщать об этих изменениях, например, для обновления UI.
В такой ситуации используют BindingList
public IList GetBindingList() {
var bl = new BindingList();
// добавляем объекты в коллекцию
for (var i = 0; i < 5; i++)
bl.Add(new Data() { Id = i, Value = DateTime.Now.Millisecond });
// по таймеру меняем значения свойств объектов в коллекции
var r = new Random();
new System.Windows.Forms.Timer() { Interval = 500, Enabled = true }
.Tick += (s, e) => {
var i = r.Next(0, bl.Count - 1);
bl[i].Value = DateTime.Now.Millisecond; };
return bl;
}
public class Data : INotifyPropertyChanged {
// требуется для уведомления о изменении значения свойств.
public event PropertyChangedEventHandler PropertyChanged = delegate {};
public int Id { get; internal set; }
public long Value {
get { return _Value; }
internal set {
if (_Value != value) {
_Value = value;
PropertyChanged(this, new PropertyChangedEventArgs("Value"));
}}}
long _Value = 0;
}
[STAThread]
static void Main(string[] args) {
var f = new Form();
var t = new Test();
var g = new DataGridView() {
Parent = f,
Dock = DockStyle.Fill,
DataSource = t.GetBindingList() };
f.ShowDialog();
}
Если не надо отслеживать изменение свойст у объектов, но требуется отслеживать изменени
колличества объектов в коллекции, то вместо BindingList можно использовать ObservableCollection.
Ответ 4
В .NET 4.0 появились ещё два полезных интерфейса: IReadOnlyCollection и IReadOnlyList (оба пока недоступны в portable-сборках).
Как понять, на каком интерфейсе остановиться? По тому, какие сценарии предполагаются для входных и выходных данных.
IEnumerable в действительности является реализацией паттерна Iterator, предоставляе
минимальный интерфейс и подходит в подавляющем большинстве случаев. Однако любимый решарпер всё время его ругает, потому что за итератором может скрываться код, который читает код из сети, с диска, с коммуникационного порта.
Так что если за интерфейсом скрывается List или Array используйте IReadOnlyCollection
Поскольку у интерфейса есть свойство Count, предполагается, что все данные доступны для повторных итераций.
Довольно трудно определить разницу между IReadOnlyCollection и IReadOnlyList. Второ
интерфейс нужен в случаях, когда порядок элеметов в коллекции важен для вызывающей стороны
Например, если коллекция реализует принципы FIFO или LIFO (очередь и стек соответственно), то это всё-таки list. Если порядок определяется вызываемой стороной (хеш-таблица, сортированной список), тогда речь идёт о collection.
Использование ICollection и IList нужно только в тех случаях, когда вы хотите дать доступ не только по чтению, но и по записи.
Ответ 5
IList наследует ICollection который в свою очередь наследует IEnumerable
IEnumerable -содержит всего 1 метод: new IEnumerator GetEnumerator();
Это базовый интерфейс для коллекций дженериков которые вам хотелось бы обрабатыват
с помощью foreach (или каким либо другим образом использовать GetEnumerator()) Подробнее тут
ICollection и IList расширяют инструментарий для работы с коллекциями(подробнее о содержании можно глянуть тут: (ICollection,IList) или тут (использовать поиск)
Другими словами выбор конкретного интерфейса зависит от того, какие операции с коллекцие
вам хотелось бы использовать. И единственная рекомендация которая тут может быть: руководствоваться здравым смыслом и учитывать задачи в которых используются объекты реализующие данные интерфейсы
А вот то где хранятся данные и как с ними осуществляется работа это уже дело класс
реализующего интерфейс. И от самого интерфейса тут ничего не зависит. Класс реализующи
данные интерфейсы, строго говоря, может реализовать их совсем непредсказуемым для вас образом и, например, тот же GetEnumerator() может возвращать вам null даже если коллекция не пустая или, например, генерировать исключение....
Комментариев нет:
Отправить комментарий