Почему ReadOnlyObservableCollection.CollectionChanged не является общедоступным?

86

Почему ReadOnlyObservableCollection.CollectionChangedзащищено, а не публично (как соответствующее ObservableCollection.CollectionChanged)?

Какая польза от реализации коллекции, INotifyCollectionChangedесли я не могу получить доступ к CollectionChangedсобытию?

Оскар
источник
1
Из любопытства, почему вы ожидаете изменения коллекции только для чтения ? Неужели тогда это будет не только для чтения?
workmad3
83
Встречный вопрос: почему бы мне не ожидать изменения ObservableCollection? Какой смысл это наблюдать, если ничего не изменится? Что ж, коллекция определенно изменится, но у меня есть потребители, которым разрешено только наблюдать за ней. Смотрят, но не трогают ...
Оскар
1
Я недавно столкнулся с этой проблемой. В основном ObservableCollection не реализует событие изменения INotifyCollection должным образом. Почему C # позволяет классу ограничивать события интерфейса доступа, но не методы интерфейса?
djskinner
35
Я должен проголосовать за то, что это полное безумие. Почему вообще ReadOnlyObservableCollection существует, если вы не можете подписаться на события CollectionChanged? В чем дело? И всем, кто твердит, что коллекция только для чтения никогда не изменится, хорошенько подумайте о том, что вы говорите.
MojoFilter
9
«только для чтения» не означает «неизменный», как некоторые думают. Это только означает, что коду, который может видеть коллекцию только через такое свойство, не разрешено изменять ее. Его действительно можно изменить, когда код добавляет или удаляет элементы через базовую коллекцию. Читатели по-прежнему должны получать уведомления о произошедших изменениях, даже если они не могут сами изменить коллекцию. Возможно, им все равно придется самостоятельно реагировать на изменения. Я не могу придумать веских причин для ограничения свойства CollectionChanged, как это было сделано в этом случае.
Гил

Ответы:

16

Я нашел для вас способ, как это сделать:

ObservableCollection<string> obsCollection = new ObservableCollection<string>();
INotifyCollectionChanged collection = new ReadOnlyObservableCollection<string>(obsCollection);
collection.CollectionChanged += new NotifyCollectionChangedEventHandler(collection_CollectionChanged);

Вам просто нужно явно сослаться на вашу коллекцию через интерфейс INotifyCollectionChanged .

Рестута
источник
14
Обратите внимание, что ReadOnlyObservableCollection - это оболочка для ObservableCollection, которая скоро изменится. Потребители ReadOnlyObservableCollection могут только наблюдать за этими изменениями, но не сами ничего менять.
Оскар
Не стоит полагаться на этот факт, коллекция только для чтения ни в коем случае не изменится, потому что она называется «только для чтения». Это мое понимание.
Рестута
4
+1 за литье. Но что касается нелогичного наблюдения за коллекцией только для чтения: если бы у вас был доступ только для чтения к базе данных, ожидали бы вы, что она никогда не изменится?
djskinner
16
Я не понимаю, в чем тут трудность. ReadOnlyObservableCollection - это класс, который обеспечивает ТОЛЬКО ДЛЯ ЧТЕНИЯ способ наблюдения за коллекцией. Если в моем классе есть коллекция, скажем, сеансов, и я не хочу, чтобы люди могли добавлять или удалять сеансы из моей коллекции, но при этом наблюдали за ними, то ReadOnlyObservableCollection - идеальное средство для этого. Коллекция доступна только для чтения пользователям моего класса, но у меня есть копия для чтения и записи для собственного использования.
scwagner
1
Просмотр кода .Net из ReadOnlyObservableCollectionвас найдет это: event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged. Явная интерфейсная реализация события.
Майк де Клерк,
7

Я знаю, что этот пост старый, однако людям следует потратить время на то, чтобы понять шаблоны, используемые в .NET, прежде чем комментировать. Коллекция только для чтения - это оболочка для существующей коллекции, которая не позволяет потребителям напрямую изменять ее; посмотрите, ReadOnlyCollectionи вы увидите, что это оболочка для коллекции, IList<T>которая может изменяться или не изменяться. Неизменяемые коллекции - это другое дело, и они охватываются новой библиотекой неизменяемых коллекций.

Другими словами, только чтение - это не то же самое, что неизменяемый !!!!

Помимо этого, ReadOnlyObservableCollectionследует неявно реализовывать INotifyCollectionChanged.

Джейсон Янг
источник
5

Определенно есть веские причины для того, чтобы подписаться на уведомления об изменении коллекции в ReadOnlyObservableCollection . Итак, в качестве альтернативы простому преобразованию вашей коллекции как INotifyCollectionChanged , если вы подклассифицируете ReadOnlyObservableCollection , то следующее обеспечивает более удобный синтаксически способ доступа к событию CollectionChanged :

    public class ReadOnlyObservableCollectionWithCollectionChangeNotifications<T> : ReadOnlyObservableCollection<T>
{
    public ReadOnlyObservableCollectionWithCollectionChangeNotifications(ObservableCollection<T> list)
        : base(list)
    {
    }

    event System.Collections.Specialized.NotifyCollectionChangedEventHandler CollectionChanged2
    {
        add { CollectionChanged += value; }
        remove { CollectionChanged -= value; }
    }
}

Раньше у меня это хорошо работало.

поркус
источник
5

Вы можете проголосовать за запись об ошибке в Microsoft Connect, в которой описывается эта проблема: https://connect.microsoft.com/VisualStudio/feedback/details/641395/readonlyobservablecollection-t-collectionchanged-event-should-be-public

Обновить:

Портал Connect был закрыт Microsoft. Так что ссылка выше больше не работает.

Библиотека My Win Application Framework (WAF) предоставляет решение: класс ReadOnlyObservableList :

public class ReadOnlyObservableList<T> 
        : ReadOnlyObservableCollection<T>, IReadOnlyObservableList<T>
{
    public ReadOnlyObservableList(ObservableCollection<T> list)
        : base(list)
    {
    }

    public new event NotifyCollectionChangedEventHandler CollectionChanged
    {
        add { base.CollectionChanged += value; }
        remove { base.CollectionChanged -= value; }
    }

    public new event PropertyChangedEventHandler PropertyChanged
    {
        add { base.PropertyChanged += value; }
        remove { base.PropertyChanged -= value; }
    }
}
jbe
источник
Подключение прекращено. Я бы проголосовал полностью
findusl
1

Как уже было сказано, у вас есть два варианта: вы можете либо привести ReadOnlyObservableCollection<T>к интерфейсу INotifyCollectionChangedдля доступа к явно реализованному CollectionChangedсобытию, либо вы можете создать свой собственный класс-оболочку, который делает это один раз в конструкторе и просто перехватывает события обернутого ReadOnlyObservableCollection<T>.

Некоторые дополнительные сведения о том, почему эта проблема еще не устранена:

Как видно из исходного кода , ReadOnlyObservableCollection<T>это открытый, незапечатанный (то есть наследуемый) класс, в котором отмечены события protected virtual.

То есть могут быть скомпилированные программы с классами, производными от ReadOnlyObservableCollection<T>, с переопределенными определениями событий, но с protectedвидимостью. Эти программы будут содержать недопустимый код, если видимость события изменится на publicв базовом классе, потому что не разрешено ограничивать видимость события в производных классах.

Так что, к сожалению, создание protected virtualсобытий publicпозже - это изменение, нарушающее двоичный код, и, следовательно, это не будет сделано без очень веских аргументов, а я боюсь, что «я должен привести объект один раз, чтобы присоединить обработчики» попросту не является.

Источник: комментарий GitHub Ника Герерры, 19 августа 2015 г.

LWChris
источник
0

Это было самым популярным в Google, поэтому я решил добавить свое решение, если другие люди его найдут.

Используя информацию выше (о необходимости преобразования в INotifyCollectionChanged ), я создал два метода расширения для регистрации и отмены регистрации.

Мое решение - методы расширения

public static void RegisterCollectionChanged(this INotifyCollectionChanged collection, NotifyCollectionChangedEventHandler handler)
{
    collection.CollectionChanged += handler;
}

public static void UnregisterCollectionChanged(this INotifyCollectionChanged collection, NotifyCollectionChangedEventHandler handler)
{
    collection.CollectionChanged -= handler;
}

пример

IThing.cs

public interface IThing
{
    string Name { get; }
    ReadOnlyObservableCollection<int> Values { get; }
}

Использование методов расширения

public void AddThing(IThing thing)
{
    //...
    thing.Values.RegisterCollectionChanged(this.HandleThingCollectionChanged);
}

public void RemoveThing(IThing thing)
{
    //...
    thing.Values.UnregisterCollectionChanged(this.HandleThingCollectionChanged);
}

Решение OP

public void AddThing(IThing thing)
{
    //...
    INotifyCollectionChanged thingCollection = thing.Values;
    thingCollection.CollectionChanged += this.HandleThingCollectionChanged;
}

public void RemoveThing(IThing thing)
{
    //...
    INotifyCollectionChanged thingCollection = thing.Values;
    thingCollection.CollectionChanged -= this.HandleThingCollectionChanged;
}

Альтернатива 2

public void AddThing(IThing thing)
{
    //...
    (thing.Values as INotifyCollectionChanged).CollectionChanged += this.HandleThingCollectionChanged;
}

public void RemoveThing(IThing thing)
{
    //...
    (thing.Values as INotifyCollectionChanged).CollectionChanged -= this.HandleThingCollectionChanged;
}
Дэн
источник
0

Решение

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

/// <summary>A wrapped <see cref="ReadOnlyObservableCollection{T}"/> that exposes the internal <see cref="CollectionChanged"/>"/>.</summary>
public class ObservableReadOnlyCollection<T> : ReadOnlyObservableCollection<T>
{
    public new NotifyCollectionChangedEventHandler CollectionChanged;

    public ObservableReadOnlyCollection(ObservableCollection<T> list) : base(list) { /* nada */ }

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs args) => 
        CollectionChanged?.Invoke(this, args);
}

Объяснение

Люди спрашивают, почему вы хотели бы наблюдать изменения в коллекции, доступной только для чтения, поэтому я объясню одну из многих возможных ситуаций; когда коллекция, доступная только для чтения, является оболочкой для частной внутренней коллекции, которая может изменяться.

Вот один из таких сценариев:

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

Обратите внимание, что для того, чтобы обернуть внутреннюю коллекцию ReadOnlyObservableCollectionвнутренней коллекцией, ObservableCollectionконструктор ReadOnlyObservableCollection.

Теперь предположим, что вы хотите уведомить потребителей службы об изменении внутренней коллекции (и, следовательно, о выставленных ReadOnlyObservableCollectionизменениях). Вместо того , чтобы качению своей собственной реализации , вы просто хотите , чтобы разоблачить CollectionChangedиз ReadOnlyObservableCollection. Вместо того, чтобы заставлять потребителя делать предположения о реализации ReadOnlyObservableCollection, вы просто меняете местами ReadOnlyObservableCollectionэтот обычай ObservableReadOnlyCollection, и все готово.

Он ObservableReadOnlyCollectionскрывается ReadOnlyObservableCollection.CollectionChangedсо своим собственным и просто передает все события изменения коллекции любому присоединенному обработчику событий.

Зодман
источник