Как открыть свойство коллекции?

Каждый раз, когда я создаю объект, у которого есть свойство коллекции, я делаю все возможное, чтобы сделать это?

  • государственная собственность с геттером, которая возвращает ссылку на закрытую переменную
  • Явный get_ObjList и set_ObjList методы, возвращающие и создающие новые или клонированные объекты каждый раз
  • Явный get_ObjList, который возвращает IEnumerator и set_ObjList, что принимает IEnumerator

Имеет ли значение значение, если коллекция представляет собой массив (т.е. objList.Clone()) в сравнении с List?

Если возврат фактической коллекции в качестве ссылки настолько плох, потому что она создает зависимости, то зачем возвращать какое-либо свойство в качестве ссылки? Каждый раз, когда вы выставляете дочерний объект в качестве ссылки, внутренности этого дочернего элемента могут быть изменены без родительского "знания", если у ребенка нет события с изменением свойства. Существует ли риск утечек памяти?

И, не нужно ли переделывать сериализацию 2 и 3? Является ли это уловкой 22 или вам нужно реализовать пользовательскую сериализацию в любое время, когда у вас есть свойство коллекции?

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

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

Неявный вопрос заключается в том, почему следует использовать один метод над другим и каковы последствия для безопасности, памяти, сериализации и т.д.?

Ответ 1

Как вы открываете коллекцию, полностью зависит от того, как пользователи должны взаимодействовать с ней.

1) Если пользователи будут добавлять и удалять элементы из коллекции объектов, то лучше всего использовать свойство коллекции только для получения (вариант №1 из исходного вопроса):

private readonly Collection<T> myCollection_ = new ...;
public Collection<T> MyCollection {
  get { return this.myCollection_; }
}

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

WPF также предоставляет некоторые настраиваемые коллекции, позволяющие пользователям отображать коллекцию элементов, которые они контролируют, например свойство ItemsSource на ItemsControl (опция №3 из исходного вопроса). Однако это не обычный вариант использования.


2). Если пользователи будут считывать данные, поддерживаемые объектом, то вы можете использовать сборку только для чтения, поскольку Quibblesome предложил:

private readonly List<T> myPrivateCollection_ = new ...;
private ReadOnlyCollection<T> myPrivateCollectionView_;
public ReadOnlyCollection<T> MyCollection {
  get {
    if( this.myPrivateCollectionView_ == null ) { /* lazily initialize view */ }
    return this.myPrivateCollectionView_;
  }
}

Обратите внимание, что ReadOnlyCollection<T> предоставляет живое представление базовой коллекции, поэтому вам нужно только создать представление один раз.

Если внутренняя коллекция не реализует IList<T> или если вы хотите ограничить доступ к более продвинутым пользователям, вместо этого вы можете переносить доступ к коллекции через счетчик:

public IEnumerable<T> MyCollection {
  get {
    foreach( T item in this.myPrivateCollection_ )
      yield return item;
  }
}

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


3) Наконец, если вам нужно выставить массивы, а не коллекции более высокого уровня, тогда вы должны вернуть копию массива, чтобы пользователи не могли его модифицировать (вариант №2 из оригинального вопроса):

private T[] myArray_;
public T[] GetMyArray( ) {
  T[] copy = new T[this.myArray_.Length];
  this.myArray_.CopyTo( copy, 0 );
  return copy;
  // Note: if you are using LINQ, calling the 'ToArray( )' 
  //  extension method will create a copy for you.
}

Вы не должны раскрывать базовый массив через свойство, так как вы не сможете сказать, когда пользователи его модифицируют. Чтобы разрешить модификацию массива, вы можете либо добавить соответствующий метод SetMyArray( T[] array ), либо использовать пользовательский индекс:

public T this[int index] {
  get { return this.myArray_[index]; }
  set {
    // TODO: validate new value; raise change event; etc.
    this.myArray_[index] = value;
  }
}

(конечно, путем создания пользовательского индексатора, вы будете дублировать работу классов BCL:)

Ответ 2

Я обычно использую это, публичный getter, который возвращает System.Collections.ObjectModel.ReadOnlyCollection:

public ReadOnlyCollection<SomeClass> Collection
{
    get
    {
         return new ReadOnlyCollection<SomeClass>(myList);
    }
}

И общедоступные методы для объекта для изменения коллекции.

Clear();
Add(SomeClass class);

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

Ответ 3

Если вы просто хотите открыть коллекцию в своем экземпляре, то использование getter/setter для переменной private member представляется наиболее разумным решением для меня (ваш первый предложенный вариант).

Ответ 4

Я разработчик Java, но я думаю, что это одинаково для С#.

Я никогда не выставляю свойство частной коллекции, потому что другие части программы могут изменять его без уведомления родителя, так что в методе getter я возвращаю массив с объектами коллекции и в методе setter, вызываю clearAll() над сборником, а затем addAll()

Ответ 5

Почему вы предлагаете использовать ReadOnlyCollection (T) - компромисс? Если вам все равно нужно получать уведомления об изменениях, сделанные на оригинальном обернутом IList, вы также можете использовать ReadOnlyObservableCollection (T), чтобы обернуть вашу коллекцию. Будет ли это менее компромиссом в вашем сценарии?

Ответ 6

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

Ответ 7

Я рекомендую использовать новые интерфейсы IReadOnlyList<T> и IReadOnlyCollection<T> для отображения коллекции (требуется .NET 4.5).

Пример:

public class AddressBook
{
    private readonly List<Contact> contacts;

    public AddressBook()
    {
        this.contacts = new List<Contact>();
    }

    public IReadOnlyList<Contact> Contacts { get { return contacts; } }

    public void AddContact(Contact contact)
    {
        contacts.Add(contact);
    }

    public void RemoveContact(Contact contact)
    {
        contacts.Remove(contact);
    }
}

Если вам нужно гарантировать, что сбор не может быть обработан снаружи, рассмотрите ReadOnlyCollection<T> или новые коллекции Immutable.

Избегайте с помощью интерфейса IEnumerable<T>, чтобы открыть коллекцию. Этот интерфейс не определяет никакой гарантии, что многократные перечисления будут работать хорошо. Если IEnumerable представляет запрос, то всякое перечисление снова выполняет запрос. Разработчики, которые получают экземпляр IEnumerable, не знают, представляет ли он коллекцию или запрос.

Подробнее об этой теме можно прочитать на этой странице Wiki.