Чому ReadOnlyObservableCollection.CollectionChanged не є загальнодоступним?


86

Чому ReadOnlyObservableCollection.CollectionChangedзахищений, а не загальнодоступний (як і відповідний ObservableCollection.CollectionChanged)?

Яка користь від реалізації колекції, INotifyCollectionChangedякщо я не можу отримати доступ до CollectionChangedподії?


1
З цікавості, чому б ви очікували зміни до збірки лише для читання ? Звичайно, тоді це не буде лише для читання?
workmad3

83
Зустрічне запитання: Чому б я не сподівався на зміну ObservableCollection? Яка користь від його спостереження, якщо нічого не зміниться? Що ж, колекція точно зміниться, але у мене є споживачі, яким дозволено лише спостерігати за нею. Дивлячись, але не торкаючись ...
Оскар

1
Нещодавно я також зіткнувся з цим питанням. В основному ObservableCollection неправильно реалізує змінену подію INotifyCollection. Чому C # дозволяє класу обмежувати інтерфейс доступу до подій, але не інтерфейсні методи?
djskinner

35
Я повинен проголосувати за те, що це суцільне божевілля. Чому у світі взагалі існує ReadOnlyObservableCollection, якщо ви не можете підписатися на події CollectionChanged? WTF - це суть? І всім, хто продовжує повторювати, що збірка, призначена лише для читання, ніколи не зміниться, подумайте про все, що ви говорите.
MojoFilter

9
"лише для читання" не означає "незмінний", як, здається, думають деякі. Це означає лише, що код, який може бачити колекцію лише через таку властивість, не має права її змінювати. Це дійсно можна змінити, коли код додає або видаляє члени через базову колекцію. Читачі все одно повинні мати можливість отримувати повідомлення про те, що зміни відбулися, навіть якщо вони не можуть змінити колекцію самостійно. Можливо, їм все одно доведеться самі реагувати на зміни. Я не можу подумати жодної вагомої причини для обмеження властивості CollectionChanged, як це було зроблено в цьому випадку.
Gil

Відповіді:



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. Явна реалізація інтерфейсу події.
Mike de Klerk

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 було вимкнено корпорацією Майкрософт. Тож посилання вище вже не працює.

Бібліотека 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; }
    }
}

Connect припинено. Я б повністю проголосував
findusl

1

Як вже відповіли, у вас є два варіанти: ви можете або ReadOnlyObservableCollection<T>передати інтерфейс INotifyCollectionChangedдля доступу до явно реалізованої CollectionChangedподії, або ви можете створити власний клас обгортки, який робить це один раз у конструкторі і просто підключає події загортання ReadOnlyObservableCollection<T>.

Деякі додаткові відомості про те, чому ця проблема ще не вирішена:

Як видно з вихідного коду , ReadOnlyObservableCollection<T>це загальнодоступний, непломбований (тобто успадковуваний) клас, де події позначені protected virtual.

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

Отож, на жаль, подальші protected virtualподії public- це бінарні зміни, і, отже, це не буде зроблено без дуже вагомих міркувань, і я боюся, що "мені потрібно один раз закинути об'єкт, щоб прикріпити обробники", це просто не так.

Джерело: коментар GitHub Ніка Герерри, 19 серпня 2015 р


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з його власним, і просто проходить по всім масиву змінених подій в будь-якому прикріпленого обробник подій.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.