Список лучших практик / создание массива / ReadOnlyCollection (и использование) - PullRequest
13 голосов
/ 06 января 2011

Мой код завален коллекциями - я полагаю, это не что-то необычное.Однако использование различных типов коллекций не является ни очевидным, ни тривиальным.Как правило, я хотел бы использовать тип, который предоставляет «лучший» API и имеет наименьший синтаксический шум.(См. Рекомендации по возврату массива значений , Использование списочных массивов - Рекомендации для сопоставимых вопросов).Существуют рекомендации, предлагающие, какие типы использовать в API , но они нецелесообразны в обычном (не API) коде.

Например:

new ReadOnlyCollection<Tuple<string,int>>(
    new List<Tuple<string,int>> {
        Tuple.Create("abc",3),
        Tuple.Create("def",37)
    }
)

List - это очень распространенная структура данных, но создание их таким образом включает в себя довольно много синтаксического шума - и это может легко ухудшиться (например, словари).Оказывается, многие списки никогда не меняются или, по крайней мере, никогда не расширяются.Конечно, ReadOnlyCollection вводит еще больше синтаксического шума, и он даже не передает, что я имею в виду;В конце концов ReadOnlyCollection может обернуть мутирующую коллекцию .Иногда я использую массив внутри и возвращаю IEnumerable, чтобы указать намерение.Но большинство из этих подходов имеют очень низкое отношение сигнал / шум;и это абсолютно важно для понимания кода.

Для 99% всего кода, который является , а не публичным API, нет необходимости следовать Framework Guidelines: однако, я все еще нужен понятный код и тип, сообщающий о намерениях.

Итак, каков наилучший способ справиться со стандартной для болота задачей создания небольших коллекций для передачи значений? Должен ли массив быть более предпочтительным, чем List где это возможно?Что-то еще целиком?Какой лучший способ - чистый, читаемый, достаточно эффективный - обойти такие маленькие коллекции?В частности, код должен быть очевидным для будущих сопровождающих, что не не читают этот вопрос и не хотят читать наборы документов API, но все еще понимают, в чем заключается цель.Также очень важно минимизировать помехи кода - поэтому такие вещи, как ReadOnlyCollection, в лучшем случае сомнительны.Нет ничего плохого в многословных типах для основных API с небольшими поверхностями, но не в качестве общей практики в большой кодовой базе.

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

Редактировать: пояснил, что речь идет о создании короткого, ясного кода, а не об открытых API.

Ответы [ 4 ]

2 голосов
/ 06 января 2011

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

В вашем классе вы можете использовать все, что лучше всего подходит для вашей текущей задачи (за / против List против Array против Dictionary против LinkedList против и т. Д.). Но это, возможно, не имеет ничего общего с тем, что вы предоставляете в своих общих свойствах или функциях.

В вашем публичном контракте (свойствах и функциях) вы должны вернуть наименьший тип (или даже лучший интерфейс), который необходим. Так что просто IList, ICollection, IDictionary, IEnumerable какого-то публичного типа. Это приводит к тому, что ваши потребительские классы просто ждут интерфейсов, а не конкретных классов, и поэтому вы можете изменить конкретную реализацию на более поздней стадии, не нарушая ваш публичный контракт (по соображениям производительности используйте List<> вместо LinkedList<> или наоборот ).

2 голосов
/ 06 января 2011

Обновление :

Итак, строго говоря, это не new ;но этот вопрос убедил меня пойти дальше и объявить проект с открытым исходным кодом, над которым я работал в течение некоторого времени (все еще в стадии разработки, но там есть кое-что полезное), который включает в себя интерфейс IArray<T> (и реализации, естественно), который, я думаю, захватывает именно то, что вы хотите здесь: индексированный, только для чтения, даже ковариантный (бонус!) интерфейс .

Некоторые преимущества:

  • Это не конкретный тип, такой как ReadOnlyCollection<T>, поэтому он не привязывает вас к конкретной реализации.
  • Этоне просто обертка (например, ReadOnlyCollection<T>), поэтому она «действительно» доступна только для чтения.
  • Она освобождает путь для некоторых действительно хороших методов расширения.Пока что в библиотеке Tao.NET есть только два (я знаю, слабых), но еще больше в пути.И вы также можете легко создать свой собственный - просто взяв из ArrayBase<T> (также в библиотеке) и переопределив свойства this[int] и Count, и все готово.

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


Мне не на 100% ясно где вы 'Вы беспокоитесь об этом "синтаксическом шуме": в вашем коде или в вызове кода?

Если вы терпимы к некоторому "шуму" в своем собственном инкапсулированном кодетогда я бы предложил обернуть массив T[] и выставить IList<T>, который выглядит как ReadOnlyCollection<T>:

class ThingsCollection
{
    ReadOnlyCollection<Thing> _things;

    public ThingsCollection()
    {
        Thing[] things = CreateThings();
        _things = Array.AsReadOnly(things);
    }

    public IList<Thing> Things
    {
        get { return _things; }
    }

    protected virtual Thing[] CreateThings()
    {
        // Whatever you want, obviously.
        return new Thing[0];
    }
}

Да, на вашем конце есть некоторый шум, ноэто не плохо.И интерфейс, который вы выставляете, довольно чистый.

Другой вариант - создать свой собственный интерфейс, что-то вроде IArray<T>, который обернет T[] и предоставляет индексатор только для получения.Тогда разоблачите это.Это в основном так же чисто, как разоблачение T[], но без ложной передачи идеи, что элементы могут быть установлены по индексу.

1 голос
/ 06 января 2011

Я не пройду вокруг Lists с, если смогу помочь.Обычно у меня есть что-то еще, что управляет рассматриваемой коллекцией, которая выставляет коллекцию, например:

public class SomeCollection
{
    private List<SomeObject> m_Objects = new List<SomeObject>();

    // ctor
    public SomeCollection()
    {
        // Initialise list here, or wot-not/
    } // eo ctor


    public List<SomeObject> Objects { get { return m_Objects; } }
} // eo class SomeCollection

И поэтому это будет объект, переданный:

public void SomeFunction(SomeCollection _collection)
{
    // work with _collection.Objects
} // eo SomeFunction

Iкак этот подход, потому что:

1) Я могу заполнить свои значения в ctor.Они там в любой момент new s SomeCollection.

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

3) Это чисто.Гораздо проще читать SomeCollection, чем List<SomeObject> везде.

4) Если вы вдруг осознаете, что ваша коллекция выбора неэффективна, вы можете изменить базовый тип коллекции, не посещая и не меняя все местаон был передан как параметр (можете ли вы представить себе проблему, скажем, с List<String>?)

0 голосов
/ 01 ноября 2011

Я согласен. IList слишком тесно связан с коллекцией ReadOnly и коллекцией Modifiable. IList должен был унаследоваться от IReadOnlyList.

Приведение обратно к IReadOnlyList не требует явного приведения. Кастинг вперед.

1

Определите свой собственный класс, который реализует IEnumerator, принимает IList в новом конструкторе, имеет свойство элемента только для чтения по умолчанию, принимающее индекс, и не включает в себя какие-либо свойства / методы, которые в противном случае позволили бы манипулировать вашим списком.

Если позже вы захотите разрешить изменение оболочки ReadOnly, как это делает IReadOnlyCollection, вы можете создать другой класс, который является оболочкой для вашей пользовательской коллекции ReadOnly, в которой будут реализованы команды Insert / Add / Remove / RemoveAt / Clear / ... и кеш эти изменения.

2

Используйте ObservableCollection / ListViewCollection и создайте свою собственную пользовательскую оболочку ReadOnlyObservableCollection, как в # 1, которая не реализует Добавление или изменение свойств и методов.

ObservableCollection может связываться с ListViewCollection таким образом, чтобы изменения в ListViewCollection не возвращались в ObservableCollection. Однако исходная коллекция ReadOnlyObservableCollection вызывает исключение, если вы пытаетесь изменить коллекцию.

Если вам нужна обратная / прямая совместимость, создайте два новых класса, унаследованных от них. Затем реализуйте IBindingList и обрабатывайте / переводите событие CollectionChanged (событие INotifyCollectionChanged) в соответствующие события IBindingList.

Затем вы можете связать его со старыми элементами управления DataGridView и WinForm, а также с элементами управления WPF / Silverlight.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...