Коллекция ‹T› против списка ‹T›, что вы должны использовать в своих интерфейсах?

Код выглядит так:

namespace Test
{
    public interface IMyClass
    {
        List<IMyClass> GetList();
    }

    public class MyClass : IMyClass
    {
        public List<IMyClass> GetList()
        {
            return new List<IMyClass>();
        }
    }
}

Когда я запускаю анализ кода, я получаю следующую рекомендацию.

Предупреждение 3 CA1002: Microsoft.Design: измените 'List' в 'IMyClass.GetList ()', чтобы использовать Collection, ReadOnlyCollection или KeyedCollection

Как мне это исправить и какие здесь хорошие практики?


person bovium    schedule 07.11.2008    source источник


Ответы (8)


Чтобы ответить на «почему» часть вопроса, почему не _ 1_, Причины - ориентированность на будущее и простота API.

Ориентация на будущее

List<T> не предназначен для простого расширяемый, создавая подклассы; он разработан, чтобы быть быстрым для внутреннего внедрения. Вы заметите, что методы на нем не виртуальные и поэтому не могут быть переопределены, и в его _ 3_ / _ 4_ / _ 5_ операций.

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

Таким образом, возвращая любой класс, который можно легко разделить на подклассы, например Collection<T> или такой интерфейс, как IList<T>, _8 _ или IEnumerable<T>, вы можете изменить свой внутренняя реализация должна быть другим типом коллекции для удовлетворения ваших потребностей, не нарушая кода потребителей, потому что он все еще может быть возвращен как тип, который они ожидают.

Простота API

List<T> содержит множество полезных операций. например BinarySearch, _ 12_ и так далее. Однако, если вы раскрываете эту коллекцию, вполне вероятно, что вы контролируете семантику списка, а не потребителей. Таким образом, хотя вашему классу внутри могут потребоваться эти операции, очень маловероятно, что потребители вашего класса захотят (или даже должны) их вызвать.

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

person Greg Beech    schedule 07.11.2008
comment
Этот ответ верен. Еще одно полезное чтение по этой теме можно найти здесь: https://web.archive.org/web/20090608080454/http://blogs.msdn.com/fxcop/archive/2006/04/27/faq-why-does-donotexposegenericlists-Recommended-that-i-expose-collection-lt-t-gt-вместо-of-list-lt-t-gt-david-kean.aspx - person senfo; 07.11.2008
comment
Я вижу ваши первые моменты, но не знаю, согласен ли я с вашей частью простоты API. - person Boris Callens; 05.03.2009
comment
stackoverflow.com/a/398988/2632991 Вот и действительно хороший пост о том, в чем разница между коллекциями и списками . - person El Mac; 24.11.2016
comment
Рабочая ссылка: blogs.msdn.microsoft.com/kcwalina/2005/09/26/ - person ofthelit; 26.11.2018
comment
Ниже я вижу, что сам Скит отстаивает интерфейсы (по крайней мере, на момент написания). Но я видел МНОЖЕСТВО ярых сторонников того, что возвращаемые типы должны быть как можно более конкретными и конкретными. Ваша позиция также заключается в том, что интерфейсы предназначены только для тех случаев, когда вы действительно видите конкретную ситуацию, когда, например, вы можете возвращать разные типы списков. Или вы считаете, что есть причина по умолчанию использовать интерфейс? - person Base; 26.08.2019

Я бы лично заявил, что он возвращает интерфейс, а не конкретную коллекцию. Если вам действительно нужен доступ по списку, используйте IList<T>. В противном случае рассмотрите ICollection<T> и _ 3_.

person Jon Skeet    schedule 07.11.2008
comment
Будет ли тогда IList расширять интерфейс ICollection? - person bovium; 07.11.2008
comment
Да, IList ‹T› расширяет ICollection ‹T›. Добавлю ссылки на документацию. - person Jon Skeet; 07.11.2008
comment
@Jon: Я знаю, что это устарело, но не могли бы вы прокомментировать то, что говорит Кшиштоф на blogs.msdn.com/b/kcwalina/archive/2005/09/26/474010.aspx? В частности, его комментарий We recommend using Collection<T>, ReadOnlyCollection<T>, or KeyedCollection<TKey,TItem> for outputs and properties and interfaces IEnumerable<T>, ICollection<T>, IList<T> for inputs. CA1002, похоже, согласуется с комментариями Кшиштофа. Я не могу себе представить, почему конкретная коллекция будет рекомендована вместо интерфейса и почему существует различие между входами / выходами. - person Nelson Rothermel; 05.10.2010
comment
@Nelson: Редко бывает, что вы хотите требовать, чтобы вызывающие абоненты передавали неизменяемый список, но разумно вернуть его, чтобы они знали, что он определенно неизменяемый. Хотя насчет других коллекций не уверен. Было бы неплохо получить более подробную информацию. - person Jon Skeet; 05.10.2010
comment
Это не для конкретного случая. Очевидно, что вообще ReadOnlyCollection<T> не имеет смысла для ввода. Точно так же IList<T>, как говорится на входе, мне нужен Sort () или какой-либо другой член, который есть в IList, который не имеет смысла для вывода. Но я имел в виду, почему ICollection<T> рекомендуется использовать как ввод, а Collection<T> как вывод. Почему не использовать ICollection<T> в качестве вывода также, как вы предложили? - person Nelson Rothermel; 05.10.2010
comment
@ Нельсон: Честно говоря, не знаю. Было бы хорошо получить дополнительную информацию от Кшиштофа. - person Jon Skeet; 05.10.2010
comment
Я думаю, это связано с неоднозначностью. Collection<T> и ReadOnlyCollection<T> оба являются производными от ICollection<T> (т.е. нет IReadOnlyCollection<T>). Если вы вернете интерфейс, неясно, какой это и можно ли его изменить. В любом случае, спасибо за ваш вклад. Для меня это была хорошая проверка. - person Nelson Rothermel; 05.10.2010

Я не думаю, что кто-то еще ответил на вопрос «почему» ... так что начнем. Причина, «почему» вам «следует» использовать Collection<T> вместо List<T>, заключается в том, что если вы выставите List<T>, то любой, кто получит доступ к вашему объекту, сможет изменить элементы в списке. В то время как Collection<T> должен указывать на то, что вы создаете свои собственные методы «Добавить», «Удалить» и т. Д.

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

Если у вас есть публичный массив, например:

public int[] MyIntegers { get; }

Вы могли бы подумать, что из-за того, что существует только метод доступа «get», никто не может вмешиваться в значения, но это неправда. Любой желающий может изменить значения внутри так:

someObject.MyIngegers[3] = 12345;

Лично я бы просто использовал List<T> в большинстве случаев. Но если вы разрабатываете библиотеку классов, которую собираетесь раздать случайным разработчикам, и вам нужно полагаться на состояние объектов ... тогда вы захотите создать свою собственную коллекцию и заблокировать ее оттуда: )

person Timothy Khouri    schedule 07.11.2008
comment
Если вы вернете List ‹T› к коду клиента, вы никогда не сможете получать уведомления, когда код клиента изменяет коллекцию. - FX COP ... См. Также msdn.microsoft.com/en-us/ library / 0fss9skc.aspx ... вау, похоже, я не ошибся :) - person Timothy Khouri; 08.11.2008
comment
Вау - годы спустя я вижу, что ссылка, которую я вставил в приведенный выше комментарий, содержала цитату в конце, поэтому она не сработала ... msdn.microsoft.com/en-us/library/0fss9skc.aspx - person Timothy Khouri; 18.10.2011

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

Не рекомендуется позволять другим объектам (или людям) напрямую изменять состояние ваших объектов. Подумайте о геттерах / установщиках свойств.

Коллекция -> Для обычной коллекции
ReadOnlyCollection -> Для коллекций, которые не следует изменять
KeyedCollection -> Если вместо этого вам нужны словари.

Как это исправить, зависит от того, что вы хотите, чтобы ваш класс делал, и цели метода GetList (). Вы можете уточнить?

person chakrit    schedule 07.11.2008
comment
Но Collection ‹T› и KeyedCollection ‹,› также не доступны только для чтения. - person nawfal; 09.07.2014
comment
@nawfal И где я сказал, что это? - person chakrit; 09.07.2014
comment
Вы заявляете не позволяете другим объектам (или людям) напрямую изменять состояние ваших объектов и перечисляете 3 типа коллекций. За исключением ReadOnlyCollection, двое других не подчиняются. - person nawfal; 09.07.2014
comment
Это относится к хорошей практике. Правильный контекст, пожалуйста. В приведенном ниже списке просто указаны основные требования к таким типам, поскольку OP хочет понять предупреждение. Затем я спрашиваю его о цели GetList(), чтобы помочь ему более правильно. - person chakrit; 09.07.2014
comment
Я никоим образом не утверждал, что использование трех типов сразу приведет вас к хорошей тренировочной позиции. - person chakrit; 09.07.2014
comment
Хорошо, я понимаю эту часть. Но все же, на мой взгляд, рассуждения неубедительны. Вы говорите, что Collection<T> помогает абстрагироваться от внутренней реализации и предотвращает прямое манипулирование внутренним списком. Как? Collection<T> - это просто оболочка, работающая с тем же переданным экземпляром. Это динамическая коллекция, предназначенная для наследования, не более того (здесь более уместен ответ Грега). - person nawfal; 09.07.2014
comment
Позвольте нам продолжить это обсуждение в чате. - person nawfal; 09.07.2014

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

person Harald Scheirich    schedule 07.11.2008

Кое-что добавить, хотя об этом давно не спрашивали.

Когда ваш тип списка является производным от List<T> вместо Collection<T>, вы не можете реализовать защищенные виртуальные методы, которые реализует Collection<T>. Это означает, что производный тип не может отвечать, если в список внесены какие-либо изменения. Это потому, что List<T> предполагает, что вы знаете, когда добавляете или удаляете элементы. Возможность отвечать на уведомления - это накладные расходы, и поэтому List<T> не предлагает их.

В случаях, когда внешний код имеет доступ к вашей коллекции, вы не можете контролировать, когда элемент добавляется или удаляется. Поэтому Collection<T> позволяет узнать, когда ваш список был изменен.

person NullReference    schedule 11.11.2015

Я не вижу проблем с возвратом чего-то вроде

this.InternalData.Filter(crteria).ToList();

Если я вернул отключенную копию внутренних данных или отдельный результат запроса данных - я могу безопасно вернуть List<TItem>, не раскрывая никаких деталей реализации, и разрешить использовать возвращенные данные удобным способом.

Но это зависит от того, какой тип потребителя я ожидаю - если это что-то вроде сетки данных, я предпочитаю возвращать IEnumerable<TItem> , который в любом случае будет скопированным списком элементов в большинстве случаев :)

person Konstantin Isaev    schedule 14.01.2012

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

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

//using System.Collections.ObjectModel;

Collection<MyClass> myCollection = new Collection<MyClass>(myList);
person Tamas Czinege    schedule 07.11.2008
comment
Извините, это была опечатка. Я меняю Коллекцию ‹MyClass›. Я действительно с нетерпением жду возможности получить в свои руки C # 4 и общую ковариацию, кстати! - person Tamas Czinege; 07.11.2008
comment
Насколько я понимаю, упаковка Collection<T> не защищает ни себя, ни базовую коллекцию. - person nawfal; 09.07.2014
comment
Этот фрагмент кода должен был угодить инструменту анализа кода. Я не думаю, что @TamasCzinege где-либо сказал, что использование Collection<T> немедленно защитит вашу базовую коллекцию. - person chakrit; 09.07.2014