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

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

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

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

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

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

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

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

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

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
48
0
22 375
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

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

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

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

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

Clear();
Add(SomeClass class);

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

Это создает новую коллекцию ReadOnlycollection при каждом чтении свойства Collection, что может быть очень ресурсоемким.

Ivan 04.09.2009 20:00

Да, Император XLII улучшает предпосылку в приведенном выше примере.

Quibblesome 11.09.2009 18:25

@Ivan На самом деле нет, это совсем не ресурсоемко, поскольку вызов этого конструктора - операция O (1), это просто оболочка. msdn.microsoft.com/en-us/library/ms132476(v=vs.110).aspx

ForceMagic 14.03.2014 19:22

Я должен был быть более конкретным. Я думал о самой обертке. Каждый вызов этого свойства создает новую оболочку. Хотя это O (1), это потребляет память и увеличивает нагрузку на сборщик мусора. В некоторых сценариях, где это свойство вызывается в цикле, вы можете закончить отправкой большого количества данных в поколение 2, которых там не должно быть. Это незначительная проблема, и сценарии, в которых она имеет значение, редки. Оглядываясь назад, я считаю, что мне вообще не следовало публиковать исходный комментарий :).

Ivan 14.03.2014 20:18

Я разработчик java, но думаю, что то же самое и с C#.

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

Ответ принят как подходящий

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

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) Если пользователи будут только читать данные, поддерживаемые объектом, то вы можете использовать коллекцию только для чтения, как предлагает Придирчивый:

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 :)

Обратите внимание, что массивы вызывают упаковку и распаковку объектов, что очень ресурсоемко.

Brettski 04.10.2008 06:26

Если массив строго типизирован (например, int [], long [] и т. д.), Доступ к элементам не вызовет бокса. Это могло бы произойти только в том случае, если бы вы использовали объект [] для хранения типов значений (например, object [] a = new {1, 2, 3}).

Emperor XLII 19.10.2008 17:35

Ах, честно, вы улучшили мое предложение. Хороший! :)

Quibblesome 11.09.2009 18:24

Реализация первого метода, который вы даете в решении 2, вызовет ошибку «Инициализатор поля не может ссылаться на нестатическое поле, метод или свойство», объясненную stackoverflow.com/questions/923343/…. НЕ объявляя myPrivateCollectionView_ как readonly, его можно установить при первом использовании свойства MyCollection. Это также более эффективно, поскольку myPrivateCollectionView_ создается и поддерживается только в том случае, если это действительно необходимо.

jphofmann 29.10.2009 21:17

@jphofmann: Да, избегать очевидных ошибок компиляции, даже в чисто иллюстративном коде, - это хорошая идея :) Я обновил первый пример кода для # 2 с учетом вашего мнения о ленивой инициализации представления.

Emperor XLII 30.10.2009 17:02

Как насчет сериализации. Я хочу сериализовать коллекцию, поэтому мне нужно открыть сеттер. Как достичь «лучших практик» для раскрытия коллекций, но разрешить сериализацию.

agarcian 13.06.2015 22:13

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

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

Я рекомендую использовать новые интерфейсы 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> или новые неизменяемые коллекции.

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

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

Другие вопросы по теме