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

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

Ответов (12)

Решение

Я следую двум правилам:

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

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

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

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

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

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

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

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

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

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

Список
List орудий IList .

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

Вы можете передать простой массив чему-то, что принимает 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> .

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

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

Еще одно отличие: IList - это интерфейс, и его нельзя создать. Список - это класс, и его можно создать. Это означает:

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

List<string> MyList = new List<string>

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

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

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

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

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

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

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

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

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

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

public class Group {
  private IList<Person> people;

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

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

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

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

ArrayList.Adapter(list).Sort()

Рекомендации 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;
    }
}