Как сделать свойство ObservableCollection доступным только для чтения?

Я хотел бы предоставить свойство модели представления, которая содержит список объектов (из базы данных).

Мне нужно, чтобы эта коллекция была доступна только для чтения. То есть я хочу предотвратить добавление / удаление и т. Д. Но разрешить работать foreach и индексаторам. Я намерен объявить частное поле, содержащее редактируемую коллекцию, и указать на него общедоступное свойство, доступное только для чтения. Следующее

public ObservableCollection<foo> CollectionOfFoo { 
     get { 
         return _CollectionOfFoo;
     }
}

Однако этот синтаксис просто предотвращает изменение ссылки на коллекцию. Это не препятствует добавлению / удалению и т. Д.

Как правильно это сделать?


person thrag    schedule 19.11.2009    source источник


Ответы (5)


[ранее] принятый ответ фактически будет возвращать другую ReadOnlyObservableCollection при каждом доступе к ReadOnlyFoo. Это расточительно и может привести к незаметным ошибкам.

Предпочтительное решение:

public class Source
{
    Source()
    {
        m_collection = new ObservableCollection<int>();
        m_collectionReadOnly = new ReadOnlyObservableCollection<int>(m_collection);
    }
 
    public ReadOnlyObservableCollection<int> Items
    {
        get { return m_collectionReadOnly; }
    }
 
    readonly ObservableCollection<int> m_collection;
    readonly ReadOnlyObservableCollection<int> m_collectionReadOnly;
}

См. ReadOnlyObservableCollection антипаттерн для полноценного обсуждения.

person Eric J.    schedule 20.05.2010
comment
Вы можете устранить необходимость в m_collectionReadOnly, сделав Items автоматическим свойством с частным установщиком и просто установив его один раз в конструкторе. Тогда у вас может быть код, который немного чище, и вам не нужно беспокоиться о создании нового экземпляра при каждом обращении к нему. - person tehDorf; 25.06.2015
comment
Теперь вы можете это сделать, хотя я не уверен, что на момент написания ответа были доступны частные установщики для автоматических свойств. Однако использование readonly приводит к еще более строгому контракту, чем то, что вы предлагаете. Если у вас есть частный сеттер, другие методы класса все еще могут теоретически изменять значение за время существования объекта. Хотя это может показаться маловероятным для простого класса, который вы пишете сегодня, имейте в виду, что код часто живет в течение многих лет с большим количеством сопровождающих. По этой причине я предпочитаю наименее разрешительный контракт. - person Eric J.; 25.06.2015
comment
Используя C # 6, теперь вы также можете использовать неявные сеттеры ...{ get; } только для чтения, которые назначаются только из конструктора. Тогда у вас есть лучшее из обоих. - person Joep Beusenberg; 22.01.2016

Мне не нравится использовать ReadOnlyObservableCollection<T>, потому что это похоже на ошибку / неработающий класс; Вместо этого я предпочитаю контрактный подход.

Вот что я использую для ковариации:

public interface INotifyCollection<T> 
       : ICollection<T>, 
         INotifyCollectionChanged
{}

public interface IReadOnlyNotifyCollection<out T> 
       : IReadOnlyCollection<T>, 
         INotifyCollectionChanged
{}

public class NotifyCollection<T> 
       : ObservableCollection<T>, 
         INotifyCollection<T>, 
         IReadOnlyNotifyCollection<T>
{}

public class Program
{
    private static void Main(string[] args)
    {
        var full = new NotifyCollection<string>();
        var readOnlyAccess = (IReadOnlyCollection<string>) full;
        var readOnlyNotifyOfChange = (IReadOnlyNotifyCollection<string>) full;


        //Covarience
        var readOnlyListWithChanges = 
            new List<IReadOnlyNotifyCollection<object>>()
                {
                    new NotifyCollection<object>(),
                    new NotifyCollection<string>(),
                };
    }
}
person Meirion Hughes    schedule 18.03.2013
comment
Действительно, само существование ReadOnlyObservableCollection - это гигантский запах дизайна. В ObservableCollection с самого начала должен был быть реализован интерфейс комбинированного представления только для чтения. - person Søren Boisen; 03.06.2015
comment
@ SørenBoisen, я согласен, ReadOnlyObservableCollection должно быть что-то IReadOnlyObservableCollection где-то в иерархии промежуточных ситуаций - person LuckyLikey; 30.06.2015
comment
Очень красиво, нарядно. Мне это нравится намного больше, чем использование класса ReadOnlyObservableCollection<T>. Тем не менее, стоит отметить, что одним из преимуществ использования ReadOnlyObservableCollection<T> является то, что сам тип просто не поддерживает модификацию. С помощью этого решения вы не защищены от неосторожных программистов, которые просто возвращают экземпляр к записываемому типу (например, NotifyCollection<T>, INotifyCollection<T>, IList<T> и т. Д.). И поверьте мне, в любой достаточно большой команде есть хотя бы один человек, который сделал бы это, если бы мог. - person Peter Duniho; 24.07.2015

Вы можете изменить тип своего свойства на IEnumerable:

public IEnumerable<foo> CollectionOfFoo { 
     get { 
         return _CollectionOfFoo;
     }
}

Я не верю, что существует стандартный интерфейс, предоставляющий индексатор. Если вам это нужно, вы можете написать интерфейс и расширить ObservableCollection для его реализации:

public interface IIndexerCollection<T> : IEnumerable<T>
{
    T this[int i]
    {
        get;
    }
}

public class IndexCollection<T> : ObservableCollection<T>, IIndexerCollection<T>
{
}
person Jake Pearson    schedule 19.11.2009
comment
Однако помните, что если CollectionOfFoo, скажем, List ‹foo›, и вызывающий абонент знает это, он может передать CollectionOfFoo обратно в List ‹foo› и выполнять операции добавления / удаления. Это работает, но не пуленепробиваемое. - person Charlie Salts; 19.11.2009
comment
Привязка к ItemsSource. Этот ответ работает, но его следует избегать. - person Jake Berger; 15.03.2012
comment
@jberger ссылка не работает - person LuckyLikey; 30.06.2015
comment
@LuckyLikey попробуйте ItemsSource Раздел примеров - person Jake Berger; 08.07.2015

Вы также можете переопределить класс списка, который вы используете, и поместить неизменяемый флаг в один из конструкторов, чтобы он не добавлял / удалял, если он был построен с неизменяемым флагом, установленным на true.

person JERiv    schedule 19.11.2009
comment
Это злоупотребление наследованием, и его следует избегать (подстановка лисков). - person RJFalconer; 05.01.2016

Используйте ReadOnlyObservableCollection ‹T>

public ReadOnlyObservableCollection<T> ReadOnlyFoo
{
    get { return new ReadOnlyObservableCollection<T> (_CollectionOfFoo); }
}

Как уже указывалось, используйте ответ Эрика Дж., Поскольку он по ошибке каждый раз возвращает новый экземпляр.

person scwagner    schedule 19.11.2009
comment
Обратите внимание на то, что INotifyCollectionChanged изменено ReadOnlyObservableCollection ‹T› защищено, и, таким образом, для доступа к подключению события вам необходимо явно указать INotifyCollectionChanged. Альтернативой может быть расширение ROOC, чтобы публично раскрыть это событие (которое должно быть в первую очередь imho) и отправить отчет об ошибке MSDN для текущей реализации. :) - person GaussZ; 12.01.2010
comment
@GaussZ Да, приятный улов, почему он не публичный? Для меня это не имеет никакого смысла. Я мог бы выделить это в новый вопрос SO, чтобы узнать, есть ли у кого-нибудь представление об этом. - person Oskar; 13.01.2010
comment
Как отметил @Eric J., возвращение новой ReadOnlyObservableCollection каждый раз, когда к свойству обращаются, затрудняет для клиентов правильную отмену подписки на события (предположительно, поэтому это ReadOnlyObservableCollection, а не просто ReadOnlyCollection). - person Bradley Grainger; 01.06.2010