Некоторые люди говорят: «всегда используйте IList<T>
вместо List<T>
».
Они хотят, чтобы вы изменили свои сигнатуры методов с void Foo(List<T> input)
на void Foo(IList<T> input)
.
Эти люди не правы.
Это более нюанс, чем это. Если вы возвращаете IList<T>
как часть общедоступного интерфейса в свою библиотеку, вы оставляете себе интересные возможности, чтобы, возможно, создать собственный список в будущем. Возможно, вам никогда не понадобится такая опция, но это аргумент. Я думаю, что это весь аргумент для возврата интерфейса вместо конкретного типа. Стоит упомянуть, но в этом случае у него есть серьезный недостаток.
В качестве незначительного контраргумента вы можете обнаружить, что каждый звонящий в любом случае нуждается в List<T>
, а код вызова набит .ToList()
Но гораздо важнее, , если вы принимаете IList в качестве параметра, вам следует быть осторожным, потому что IList<T>
и List<T>
не ведут себя одинаково. Несмотря на сходство в названии, и несмотря на то, что они имеют общий интерфейс , они не выставляют тот же контракт .
Предположим, у вас есть этот метод:
public Foo(List<int> a)
{
a.Add(someNumber);
}
Полезный коллега «рефакторинг» метод принятия IList<int>
.
Ваш код теперь не работает, потому что int[]
реализует IList<int>
, но имеет фиксированный размер. Для контракта на ICollection<T>
(основа IList<T>
) требуется код, который использует его для проверки флага IsReadOnly
перед попыткой добавления или удаления элементов из коллекции. Контракт на List<T>
не имеет.
Принцип замещения Лискова (упрощенный) гласит, что производный тип должен использоваться вместо базового типа без каких-либо дополнительных предварительных условий или постусловий.
Такое ощущение, что это нарушает принцип подстановки Лискова.
int[] array = new[] {1, 2, 3};
IList<int> ilist = array;
ilist.Add(4); // throws System.NotSupportedException
ilist.Insert(0, 0); // throws System.NotSupportedException
ilist.Remove(3); // throws System.NotSupportedException
ilist.RemoveAt(0); // throws System.NotSupportedException
Но это не так. Ответ на этот вопрос заключается в том, что в данном примере IList / ICollection неверен. Если вы используете ICollection , вам необходимо проверить флаг IsReadOnly.
if (!ilist.IsReadOnly)
{
ilist.Add(4);
ilist.Insert(0, 0);
ilist.Remove(3);
ilist.RemoveAt(0);
}
else
{
// what were you planning to do if you were given a read only list anyway?
}
Если кто-то передаст вам массив или список, ваш код будет работать нормально, если вы каждый раз проверяете флаг и у вас есть запасной вариант ... Но на самом деле; кто так делает? Не знаете заранее, нужен ли вашему методу список, который может принимать дополнительных членов; Вы не указываете это в сигнатуре метода? Что именно вы собираетесь делать, если вам передают список только для чтения, например int[]
?
Вы можете заменить List<T>
на код, который использует IList<T>
/ ICollection<T>
, правильно . Вы не можете гарантировать, что вы можете заменить IList<T>
/ ICollection<T>
на код, который использует List<T>
.
Существует призыв к принципу единой ответственности / принципу разделения интерфейсов во многих аргументах, чтобы использовать абстракции вместо конкретных типов - в зависимости от самого узкого интерфейса. В большинстве случаев, если вы используете List<T>
и думаете, что вместо этого можете использовать более узкий интерфейс - почему бы не IEnumerable<T>
? Это часто лучше подходит, если вам не нужно добавлять элементы. Если вам нужно добавить в коллекцию, используйте конкретный тип, List<T>
.
Для меня IList<T>
(и ICollection<T>
) - худшая часть .NET Framework. IsReadOnly
нарушает принцип наименьшего удивления. Класс, такой как Array
, который никогда не позволяет добавлять, вставлять или удалять элементы, не должен реализовывать интерфейс с методами Add, Insert и Remove. (см. также https://softwareengineering.stackexchange.com/questions/306105/implementing-an-interface-when-you-dont-need-one-of-the-properties)
IList<T>
подходит для вашей организации? Если коллега попросит вас изменить сигнатуру метода для использования IList<T>
вместо List<T>
, спросите их, как они добавят элемент в IList<T>
. Если они не знают о IsReadOnly
(а большинство людей не знают), то не используйте IList<T>
. Когда-либо.
Обратите внимание, что флаг IsReadOnly происходит от ICollection и указывает, могут ли элементы быть добавлены или удалены из коллекции; но просто чтобы действительно запутать вещи, это не указывает, могут ли они быть заменены, что в случае массивов (которые возвращают IsReadOnlys == true) может быть.
Подробнее об IsReadOnly см. msdn определение ICollection .IsReadOnly