Каждый раз, когда я создаю объект, у которого есть свойство коллекции, я ищу лучший способ сделать это?
Имеет ли значение, является ли коллекция массивом (например, objList.Clone ()) или списком?
Если возвращать фактическую коллекцию в качестве ссылки так плохо, потому что она создает зависимости, тогда зачем возвращать какое-либо свойство в качестве ссылки? Каждый раз, когда вы предоставляете дочерний объект в качестве ссылки, внутреннее устройство этого дочернего объекта может быть изменено без «ведома» родителя, если только у дочернего объекта нет события изменения свойства. Есть ли риск утечки памяти?
И разве варианты 2 и 3 не нарушают сериализацию? Это уловка 22 или вам нужно реализовать настраиваемую сериализацию в любое время, когда у вас есть свойство коллекции?
Общий ReadOnlyCollection кажется хорошим компромиссом для общего использования. Он оборачивает список IList и ограничивает доступ к нему. Возможно, это помогает с утечками памяти и сериализацией. Однако у него все еще есть проблемы перечисления
Может быть, это просто зависит от обстоятельств. Если вам все равно, что коллекция изменена, просто выставьте ее как общедоступный метод доступа к частной переменной для # 1. Если вы не хотите, чтобы другие программы изменяли коллекцию, то лучше №2 и / или №3.
Подразумевается вопрос: почему один метод должен использоваться вместо другого и каковы последствия для безопасности, памяти, сериализации и т. д.?





Если вы просто хотите открыть коллекцию в своем экземпляре, то использование геттера / сеттера для частной переменной-члена кажется мне наиболее разумным решением (ваш первый предложенный вариант).
Обычно я использую публичный метод получения, который возвращает System.Collections.ObjectModel.ReadOnlyCollection:
public ReadOnlyCollection<SomeClass> Collection
{
get
{
return new ReadOnlyCollection<SomeClass>(myList);
}
}
И общедоступные методы объекта для изменения коллекции.
Clear();
Add(SomeClass class);
Если класс должен быть репозиторием, с которым могут связываться другие люди, я просто выставляю частную переменную в соответствии с методом № 1, поскольку он экономит написание собственного API, но я стараюсь избегать этого в производственном коде.
Да, Император XLII улучшает предпосылку в приведенном выше примере.
@Ivan На самом деле нет, это совсем не ресурсоемко, поскольку вызов этого конструктора - операция O (1), это просто оболочка. msdn.microsoft.com/en-us/library/ms132476(v=vs.110).aspx
Я должен был быть более конкретным. Я думал о самой обертке. Каждый вызов этого свойства создает новую оболочку. Хотя это O (1), это потребляет память и увеличивает нагрузку на сборщик мусора. В некоторых сценариях, где это свойство вызывается в цикле, вы можете закончить отправкой большого количества данных в поколение 2, которых там не должно быть. Это незначительная проблема, и сценарии, в которых она имеет значение, редки. Оглядываясь назад, я считаю, что мне вообще не следовало публиковать исходный комментарий :).
Я разработчик 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 :)
Обратите внимание, что массивы вызывают упаковку и распаковку объектов, что очень ресурсоемко.
Если массив строго типизирован (например, int [], long [] и т. д.), Доступ к элементам не вызовет бокса. Это могло бы произойти только в том случае, если бы вы использовали объект [] для хранения типов значений (например, object [] a = new {1, 2, 3}).
Ах, честно, вы улучшили мое предложение. Хороший! :)
Реализация первого метода, который вы даете в решении 2, вызовет ошибку «Инициализатор поля не может ссылаться на нестатическое поле, метод или свойство», объясненную stackoverflow.com/questions/923343/…. НЕ объявляя myPrivateCollectionView_ как readonly, его можно установить при первом использовании свойства MyCollection. Это также более эффективно, поскольку myPrivateCollectionView_ создается и поддерживается только в том случае, если это действительно необходимо.
@jphofmann: Да, избегать очевидных ошибок компиляции, даже в чисто иллюстративном коде, - это хорошая идея :) Я обновил первый пример кода для # 2 с учетом вашего мнения о ленивой инициализации представления.
Как насчет сериализации. Я хочу сериализовать коллекцию, поэтому мне нужно открыть сеттер. Как достичь «лучших практик» для раскрытия коллекций, но разрешить сериализацию.
Почему вы предлагаете использовать 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, не знают, представляет ли он коллекцию или запрос.
Подробнее об этой теме можно прочитать на этом Вики-страница.
Это создает новую коллекцию ReadOnlycollection при каждом чтении свойства Collection, что может быть очень ресурсоемким.