TL; DR , компилятор C # не имеет автоколлекций, поскольку существует множество различных способов экспонирования коллекций. Представляя коллекцию, вы должны тщательно продумать, как вы хотите, чтобы коллекция была инкапсулирована, и использовать правильный метод.
Причина, по которой компилятор C # предоставляет авто-свойства, заключается в том, что они являются общими и почти всегда работают одинаково, однако, как вы обнаруживаете, ситуация редко бывает такой простой при работе с коллекциями - существует много разные способы разоблачения коллекции, правильный метод всегда зависит от ситуации, если назвать несколько:
1) Коллекция, которую можно изменить
Часто нет реальной необходимости накладывать какие-либо реальные ограничения на выставленную коллекцию:
public List<T> Collection
{
get
{
return this.collection;
}
set
{
if (value == null)
{
throw new ArgumentNullException();
}
this.collection = value;
}
}
private List<T> collection = new List<T>();
Это может быть хорошей идеей, чтобы убедиться, что коллекция никогда не равна нулю, в противном случае вы можете просто использовать авто-свойства. Если у меня нет веской причины желать большего инкапсуляции своей коллекции, я всегда использую этот метод для простоты.
2) Коллекция, которую можно изменить, но не поменять местами
Вы можете кодировать это так, как вам нравится, но идея та же: открытая коллекция позволяет изменять элементы, но сама базовая коллекция не может быть заменена другой коллекцией. Например:
public IList<T> Collection
{
get
{
return this.collection;
}
}
private ObservableCollection<T> collection = new ObservableCollection<T>();
Я склонен использовать этот простой шаблон при работе с такими вещами, как наблюдаемые коллекции , когда потребитель должен иметь возможность изменять коллекцию, но я подписан на уведомления об изменениях - если вы разрешите потребителям менять всю коллекцию тогда у вас просто начнутся головные боли.
3) Предоставить доступную только для чтения копию коллекции
Часто вы хотите запретить пользователям изменять выставленную коллекцию - обычно, однако, вы do хотите, чтобы выставляющий класс мог изменять коллекцию. Простой способ сделать это - открыть копию вашей коллекции только для чтения:
public ReadOnlyCollection<T> Collection
{
get
{
return new ReadOnlyCollection<T>(this.collection);
}
}
private List<T> collection = new List<T>();
Это связано со свойством, что возвращаемая коллекция никогда не изменяется, даже если базовая коллекция изменяется. Это часто хорошо, так как позволяет потребителям перебирать возвращенную коллекцию, не опасаясь, что она может быть изменена:
foreach (var item in MyClass.Collection)
{
// This is safe - even if MyClass changes the underlying collection
// we won't be affected as we are working with a copy
}
Однако это не всегда ожидаемое (или желаемое) поведение - например, свойство Controls
не работает таким образом. Следует также учитывать, что копирование многих больших коллекций таким способом потенциально неэффективно.
При отображении коллекций, которые доступны только для чтения, всегда следует помнить, что элементы в элементе управления можно по-прежнему изменять. Опять же, это может быть хорошо, но если вы хотите, чтобы открытая коллекция была «полностью» неизменяемой, то убедитесь, что элементы в коллекции также доступны только для чтения / неизменны (например, System.String
).
4) Коллекции, которые могут быть изменены, но только определенным образом
Предположим, вы хотите открыть коллекцию, в которую можно добавлять, а не удалять элементы? Вы можете выставить свойства на самом выставляющем классе:
public ReadOnlyCollection<T> Collection
{
get
{
return new ReadOnlyCollection<T>(this.collection);
}
}
private List<T> collection = new List<T>();
public AddItem(T item);
Однако, если у вашего объекта много таких коллекций, ваш интерфейс может быстро запутаться и запутаться. Также я нахожу, что эта модель временами может показаться нелогичной:
var collection = MyClass.Collection;
int count = collection.Count;
MyClass.AddItem(item);
Debug.Assert(collection.Count > count, "huh?");
Это намного больше усилий, но IMO более аккуратный метод состоит в том, чтобы представить пользовательскую коллекцию, которая инкапсулирует вашу "реальную" коллекцию, и правила о том, как коллекция может и не может быть изменена, например:
public sealed class CustomCollection<T> : IList<T>
{
private IList<T> wrappedCollection;
public CustomCollection(IList<T> wrappedCollection)
{
if (wrappedCollection == null)
{
throw new ArgumentNullException("wrappedCollection");
}
this.wrappedCollection = wrappedCollection;
}
// "hide" methods that don't make sense by explicitly implementing them and
// throwing a NotSupportedException
void IList<T>.RemoveAt(int index)
{
throw new NotSupportedException();
}
// Implement methods that do make sense by passing the call to the wrapped collection
public void Add(T item)
{
this.wrappedCollection.Add(item);
}
}
Пример использования:
public MyClass()
{
this.wrappedCollection = new CustomCollection<T>(this.collection)
}
public CustomCollection<T> Collection
{
get
{
return this.wrappedCollection;
}
}
private CustomCollection<T> wrappedCollection;
private List<T> collection = new List<T>();
Открытая коллекция теперь инкапсулирует наши правила о том, как коллекция может и не может быть изменена, а также немедленно отражает изменения, внесенные в базовую коллекцию (что может или не может быть хорошей вещью). Это также потенциально более эффективно для больших коллекций.