Подтвердить что ты не робот

Список лучших практик/создание массива /ReadOnlyCollection (и использование)

Мой код усеян коллекциями - я не необычная вещь. Однако использование различных типов коллекций не является очевидным и тривиальным. Как правило, я хотел бы использовать тип, который предоставляет "лучший" 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: однако мне все еще нужен понятный код и тип, который сообщает о намерениях.

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

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

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

4b9b3361

Ответ 1

Обновление

Таким образом, это не является строго новым; но этот вопрос убедил меня продолжить и объявить проект с открытым исходным кодом У меня было какое-то время в работе (все еще работа продолжается, но там есть какой-то полезный материал), который включает в себя интерфейс 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[] и предоставляет индексирующее средство get-only. Тогда выложите это. Это в основном так же чисто, как и просмотр T[], но без ложной передачи идеи, что элементы могут быть установлены по индексу.

Ответ 2

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

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

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

Ответ 3

Я не обойти 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

Мне нравится этот подход, потому что:

1) Я могу заполнить свои значения в ctor. Они там, темный кто-нибудь new SomeCollection.

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

3) Он чист. Намного легче читать SomeCollection чем List<SomeObject> везде.

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

Ответ 4

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

Возвращение в IReadOnlyList не потребует явного приведения. Кастинг вперед был бы.

1.

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

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

2.

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

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

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

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