Когда использовать IList и когда использовать List


180

Я знаю, что IList - это интерфейс, а List - конкретный тип, но я до сих пор не знаю, когда использовать каждый из них. Что я делаю сейчас, если мне не нужны методы Sort или FindAll, я использую интерфейс. Я прав? Есть ли лучший способ решить, когда использовать интерфейс или конкретный тип?


1
Если кому-то все еще интересно, я найду лучшие ответы здесь: stackoverflow.com/questions/400135/listt-or-ilistt
Крисмограмма

Ответы:


175

Есть два правила, которым я следую:

  • Принять самый основной тип, который будет работать
  • Верните самый богатый тип, который понадобится вашему пользователю

Поэтому при написании функции или метода, который принимает коллекцию, пишите ее не для List, а для IList <T>, ICollection <T> или IEnumerable <T>. Универсальные интерфейсы по-прежнему будут работать даже для разнородных списков, поскольку System.Object также может быть буквой T. Это избавит вас от головной боли, если вы решите использовать стек или другую структуру данных в будущем. Если все, что вам нужно сделать в функции - это foreach, то IEnumerable <T> - это действительно все, о чем вы должны просить.

С другой стороны, когда вы возвращаете объект из функции, вы хотите предоставить пользователю максимально богатый набор операций без необходимости их наложения. Так что в этом случае, если это внутренний список <T>, верните копию в виде списка <T>.


43
Вы не должны относиться к типам ввода / вывода по-другому. Входные и выходные типы должны как наиболее базовый тип (предпочтительно интерфейс) , который будет поддерживать потребности клиентов. Инкапсуляция основана на том, чтобы как можно меньше рассказывать клиентам о реализации вашего класса. Если вы вернете конкретный список, вы не сможете изменить его на какой-либо другой, более лучший, не заставляя всех ваших клиентов перекомпилировать / обновлять.
Пепел

11
Я не согласен с двумя правилами ... Я бы использовал наиболее примитивный тип и specialy при возврате в этом случае IList (лучше IEnumarable), и вы должны работать со List внутри своей функции. Затем, когда вам нужно «добавить» или «отсортировать», используйте коллекцию, если нужно больше, используйте список. Так что мое жесткое правило было бы: START всегда с IENumarable и если вам нужно больше , чем продлить ...
Этхемы

2
Для вашего удобства у «двух правил» есть название: принцип робастности (он же закон Постеля) .
easoncxz

Независимо от того, на чьей стороне споры о том, возвращать ли самый базовый тип или самый богатый тип, следует учитывать, что при возврате очень упрощенного интерфейса часто потребляющий код может - хотя и не всегда - использовать if...elseцепочку с isключевым словом для вычисления гораздо более богатый тип для него и в конечном итоге приведение к нему и использование этого в любом случае. Таким образом, вы не обязательно гарантируете что-либо скрыть, используя простой интерфейс, вместо того, чтобы просто скрывать это. Однако усложнение может также заставить автора потребляющего кода дважды подумать о том, как он его использует.
Panzercrisis

6
Я очень сильно не согласен по поводу пункта № 2, особенно если он находится на границе сервиса / API. Возвращение изменяемых коллекций может создать впечатление, что коллекции являются «живыми» и вызывают методы, подобные Add()и Remove()могут иметь последствия, выходящие за рамки просто коллекции. Возврат интерфейса, доступного только для чтения, например, IEnumerableчасто используется для методов извлечения данных. Ваш потребитель может проецировать его в более богатый тип по мере необходимости.
STW

56

Руководства Microsoft, проверенные FxCop, не рекомендуют использовать List <T> в общедоступных API-интерфейсах - предпочитайте IList <T>.

Кстати, теперь я почти всегда объявляю одномерные массивы как IList <T>, что означает, что я могу последовательно использовать свойство IList <T> .Count, а не Array.Length. Например:

public interface IMyApi
{
    IList<int> GetReadOnlyValues();
}

public class MyApiImplementation : IMyApi
{
    public IList<int> GetReadOnlyValues()
    {
        List<int> myList = new List<int>();
        ... populate list
        return myList.AsReadOnly();
    }
}
public class MyMockApiImplementationForUnitTests : IMyApi
{
    public IList<int> GetReadOnlyValues()
    {
        IList<int> testValues = new int[] { 1, 2, 3 };
        return testValues;
    }
}

3
Мне больше всего нравится это объяснение / пример!
JONH

28

Есть важная вещь, которую люди, кажется, всегда упускают из виду:

Вы можете передать простой массив чему-то, что принимает IList<T>параметр, а затем вы можете вызвать IList.Add()и получить исключение времени выполнения:

Unhandled Exception: System.NotSupportedException: Collection was of a fixed size.

Например, рассмотрим следующий код:

private void test(IList<int> list)
{
    list.Add(1);
}

Если вы вызовете это следующим образом, вы получите исключение времени выполнения:

int[] array = new int[0];
test(array);

Это происходит потому, что использование простых массивов с IList<T>нарушает принцип подстановки Лискова.

По этой причине, если вы звоните, IList<T>.Add()вы можете рассмотреть вопрос о необходимости List<T>вместо IList<T>.


Это тривиально верно для каждого интерфейса. Если вы хотите довести доводы до конца, вы можете поспорить, что никогда не будете использовать какой-либо интерфейс вообще, потому что его реализация может привести к отказу. Если, с другой стороны, рассмотрим предложение , данное ОП предпочитать List<T>более IList<T>, вы также должны быть осведомлены о причинах , почему IList<T>рекомендуется. (Например, blogs.msdn.microsoft.com/kcwalina/2005/09/26/… )
Миха Виденманн,

3
@MichaWiedenmann Мой ответ здесь зависит от того, когда вы звоните IList<T>.Add(). Я не говорю , что вы не должны использовать IList<T>- Я просто указывая на возможную ловушку. (Я склонен использовать IEnumerable<T>или IReadOnlyList<T>или IReadOnlyCollection<T>предпочитаю, IList<T>если смогу.)
Мэтью Уотсон,

24

Я бы согласился с советом Ли о том, чтобы брать параметры, но не возвращать.

Если вы указываете свои методы для возврата интерфейса, это означает, что вы можете в дальнейшем изменить точную реализацию, даже не узнав о потребляющем методе. Я думал, что мне никогда не понадобится переходить из List <T>, но позже пришлось изменить его, чтобы использовать настраиваемую библиотеку списков для дополнительной функциональности, которую она предоставляла. Поскольку я только возвратил IList <T>, ни один из людей, которые использовали библиотеку, не должен был менять свой код.

Конечно, это нужно применять только к методам, которые являются внешне видимыми (например, общедоступные методы). Я лично использую интерфейсы даже во внутреннем коде, но, поскольку вы можете изменить весь код самостоятельно, если вносите критические изменения, это не является строго необходимым.


22

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

IList
IList реализует IEnumerable.
Вы должны использовать, IListкогда вам нужен доступ по индексу к вашей коллекции, добавлять и удалять элементы и т.д ...

Список
List инструментов IList.


3
Отличный, четкий ответ, который я пометил как полезный. Однако я хотел бы добавить, что для большинства разработчиков большую часть времени не стоит беспокоиться о крошечной разнице в размере и производительности программы: в случае сомнений просто используйте List.
Грэхэм Лайт

9

Всегда лучше использовать минимально возможный базовый тип. Это дает разработчику вашего интерфейса или потребителю вашего метода возможность использовать все что угодно за кулисами.

Для коллекций вы должны стремиться использовать IEnumerable, где это возможно. Это дает большую гибкость, но не всегда подходит.


1
Всегда лучше принять минимально возможный базовый тип. Возвращение это другая история. Выберите, какие варианты могут быть полезны. Итак, вы думаете, что ваш клиент может захотеть использовать индексированный доступ? Не ToList()позволяйте им возвращать возвращенный IEnumerable<T>список, а IList<T>вместо этого возвращайте . Теперь клиенты могут извлечь выгоду из того, что вы можете предоставить без труда.
Тимо

5

Если вы работаете в одном методе (или даже в одном классе или сборке в некоторых случаях), и никто за пределами не увидит, что вы делаете, используйте полноту List. Но если вы взаимодействуете с внешним кодом, как, например, когда вы возвращаете список из метода, вам нужно только объявить интерфейс, не привязывая себя к конкретной реализации, особенно если вы не можете контролировать, кто компилирует против вашего код потом. Если вы начали с конкретного типа и решили заменить его на другой, даже если он использует тот же интерфейс, вы нарушите чужой код, если только вы не начали с интерфейса или абстрактного базового типа.


4

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

Например, допустим, у вас есть Personкласс и Groupкласс. В Groupэкземпляре много людей, поэтому список здесь имеет смысл. Когда я объявляю объект списка в, Groupя буду использовать IList<Person>и создавать его как List.

public class Group {
  private IList<Person> people;

  public Group() {
    this.people = new List<Person>();
  }
}

И, если вам даже не нужно все в IListвас, вы всегда можете использовать IEnumerableтоже. С современными компиляторами и процессорами, я не думаю, что есть какая-то разница в скорости, так что это больше вопрос стиля.


3
почему бы не сделать его просто списком в первую очередь? Я до сих пор не понимаю, почему вы получаете бонус от создания IList, тогда как в конструкторе вы превращаете его в List <>
chobo2

Я согласен, если вы явно создаете объект List <T>, то вы теряете преимущество интерфейса?
The_Butcher

4

Чаще всего вам лучше использовать наиболее общий используемый тип, в данном случае IList или даже лучше интерфейс IEnumerable, чтобы можно было удобно переключать реализацию в более позднее время.

Однако в .NET 2.0 есть одна неприятная вещь - у IList нет метода Sort () . Вместо этого вы можете использовать прилагаемый адаптер:

ArrayList.Adapter(list).Sort()

2

Вы должны использовать интерфейс только в том случае, если он вам нужен, например, если ваш список приведен к реализации IList, отличной от List. Это верно, когда, например, вы используете NHibernate, который при получении данных преобразует ILists в объект-мешок NHibernate.

Если List - единственная реализация, которую вы когда-либо будете использовать для определенной коллекции, не стесняйтесь объявлять ее как конкретную реализацию List.


1

В ситуациях, с которыми я обычно сталкиваюсь, я редко использую IList напрямую.

Обычно я просто использую это как аргумент метода

void ProcessArrayData(IList almostAnyTypeOfArray)
{
    // Do some stuff with the IList array
}

Это позволит мне выполнять общую обработку практически любого массива в среде .NET, если только он не использует IEnumerable, а не IList, что иногда случается.

Это действительно сводится к той функциональности, которая вам нужна. Я бы предложил использовать класс List в большинстве случаев. IList лучше всего подходит для случаев, когда вам нужно создать собственный массив, который может иметь некоторые очень специфические правила, которые вы хотите инкапсулировать в коллекцию, чтобы не повторяться, но при этом хотеть, чтобы .NET распознал его как список.


1

Объект AList позволяет создавать список, добавлять в него элементы, удалять его, обновлять, индексировать в него и т. Д. Список используется всякий раз, когда вам нужен просто общий список, в котором вы указываете тип объекта и все.

IList, с другой стороны, является интерфейсом. По сути, если вы хотите создать свой собственный тип List, скажем, класс списка с именем BookList, то вы можете использовать интерфейс, чтобы дать вам базовые методы и структуру для вашего нового класса. IList предназначен для случаев, когда вы хотите создать свой собственный специальный подкласс, который реализует List.

Другое отличие: IList является интерфейсом и не может быть создан. Список является классом и может быть создан. Это значит:

IList<string> MyList = new IList<string>();

List<string> MyList = new List<string>
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.