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

162

Код выглядит следующим образом:

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

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

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

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

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

bovium
источник

Ответы:

234

Чтобы ответить на вопрос «почему» на вопрос «почему бы и нет» List<T>, причинами являются перспектива на будущее и простота API.

Будущие корректуры

List<T>не предназначен для того, чтобы быть легко расширяемым за счет его подкласса; это разработано, чтобы быть быстрым для внутренних реализаций. Вы заметите, что методы на нем не являются виртуальными и поэтому не могут быть переопределены, и нет никаких хуков в его Add/ Insert/ Removeоперациях.

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

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

API простота

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

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

Грег Бич
источник
1
Этот ответ мертв. Еще одно хорошее прочтение на эту тему можно найти здесь: blogs.msdn.com/fxcop/archive/2006/04/27/…
senfo
7
Я вижу твои первые замечания, но я не знаю, согласен ли я с твоей частью простоты API.
Борис Калленс
stackoverflow.com/a/398988/2632991 Вот и очень хороший пост о том, в чем разница между коллекциями и списками.
El Mac
2
blogs.msdn.com/fxcop/archive/2006/04/27/… -> Мертв . У нас есть более новая информация для этого?
Мачадо
1
Рабочая ссылка: blogs.msdn.microsoft.com/kcwalina/2005/09/26/…
ofthelit
50

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

Джон Скит
источник
1
Будет ли IList расширять интерфейс ICollection?
Бовиум
8
@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, похоже , согласуется с комментариями Кшиштофа. Я не могу себе представить, почему вместо интерфейса была бы рекомендована конкретная коллекция и почему существует различие между входами / выходами.
Нельсон Ротермел
3
@Nelson: Это редкое , что вы хотите , чтобы потребовать звонящих пройти в неизменяемой списке, но это разумно возвращать один , так что они знают , что это, безусловно , неизменен. Не уверен насчет других коллекций, хотя. Было бы неплохо иметь больше деталей.
Джон Скит
2
Это не для конкретного случая. Очевидно, что вообще ReadOnlyCollection<T>не имеет смысла для ввода. Точно так же, IList<T>как входные данные говорят: «Мне нужен Sort () или какой-то другой член, который есть в IList», что не имеет смысла для выходных данных. Но я имел в виду то, что было ICollection<T>бы рекомендовано в качестве входных данных и Collection<T>выходных данных. Почему бы не использовать ICollection<T>в качестве вывода также, как вы предложили?
Нельсон Ротермел
9
Я думаю, что это связано с однозначностью. Collection<T>и ReadOnlyCollection<T>оба происходят от ICollection<T>(то есть нет IReadOnlyCollection<T>). Если вы вернете интерфейс, неясно, какой он есть и можно ли его изменить. В любом случае, спасибо за ваш вклад. Это была хорошая проверка здравомыслия для меня.
Нельсон Ротермел
3

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

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

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

public int[] MyIntegers { get; }

Можно подумать, потому что есть только метод доступа get, который никто не может связать со значениями, но это не так. Любой может изменить значения внутри, вот так:

someObject.MyIngegers[3] = 12345;

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

Тимоти Хоури
источник
«Если вы вернете List <T> клиентскому коду, вы никогда не сможете получать уведомления, когда клиентский код изменяет коллекцию». - FX COP ... См. Также " msdn.microsoft.com/en-us/library/0fss9skc.aspx " ... вау, кажется, я все же не с базы :)
Тимоти Хоури
Вау - спустя годы я вижу, что ссылка, которую я вставил в приведенный выше комментарий, в конце содержала цитату, поэтому она не работала ... msdn.microsoft.com/en-us/library/0fss9skc.aspx
Тимоти Хоури
2

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

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

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

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

chakrit
источник
1
Но Collection <T> и KeyedCollection <,> также не доступны для чтения.
Nawfal
@nawfal А где я сказал, что это?
Чакрит
1
Вы заявляете, что не позволяете другим объектам (или людям) напрямую изменять состояние ваших объектов и перечисляете 3 типа коллекций. Запрещение ReadOnlyCollectionдвух других не подчиняется этому.
nawfal
Это относится к «хорошей практике». Правильный контекст, пожалуйста. В приведенном ниже списке просто указывается основное требование таких типов, поскольку ФП хочет понять предупреждение. Затем я продолжаю спрашивать его о том, GetList()как помочь ему более правильно.
чакрит
1
Хорошо, хорошо, я понимаю эту часть. Но все же аргументирующая часть не является правильной, по моему мнению. Вы говорите, Collection<T>помогает в абстрагировании внутренней реализации и предотвращает прямое манипулирование внутренним списком. Как? Collection<T>это просто оболочка, работающая на том же переданном экземпляре. Это динамическая коллекция, предназначенная для наследования, и ничего более (ответ Грега здесь более актуален).
nawfal
1

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

Харальд Шейрих
источник
1

Что-то, чтобы добавить, хотя это было давно, так как это спросили.

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

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

NullReference
источник
0

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

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

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

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

Константин Исаев
источник
-1

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

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

//using System.Collections.ObjectModel;

Collection<MyClass> myCollection = new Collection<MyClass>(myList);
Тамас Чинеге
источник
1
Извините, это была опечатка. Я храню коллекцию <MyClass>. Я действительно с нетерпением жду, чтобы получить в свои руки C # 4 и общую ковариацию, кстати!
Тамас Чинеге
Collection<T>Насколько я понимаю, упаковка в себя не защищает ни себя, ни основную коллекцию.
Nawfal
Этот кусок кода был «пожалуйста, инструмент анализа кода». Я не думаю, что @TamasCzinege сказал, что использование Collection<T>сразу же защитит вашу основную коллекцию.
чакрит