Perché ReadOnlyObservableCollection.CollectionChanged non è pubblico?


88

Perché è ReadOnlyObservableCollection.CollectionChangedprotetto e non pubblico (come lo ObservableCollection.CollectionChangedè il corrispondente )?

A cosa serve l'implementazione di una raccolta INotifyCollectionChangedse non riesco ad accedere CollectionChangedall'evento?


1
Per curiosità, perché dovresti essere in attesa di un insieme di sola lettura per il cambiamento? Sicuramente allora non sarebbe solo letto?
workmad3

84
Contro domanda: perché non dovrei aspettarmi che una ObservableCollection cambi? A che serve osservarlo se non cambierà nulla? Ebbene, la collezione cambierà sicuramente, ma ho dei consumatori che possono solo osservarla. Guardare ma non toccare ...
Oskar

1
Di recente mi sono imbattuto anche in questo problema. Fondamentalmente ObservableCollection non implementa correttamente l'evento modificato INotifyCollection. Perché C # consente a una classe di limitare gli eventi di interfaccia di accesso ma non i metodi di interfaccia?
djskinner

36
Devo esprimere il mio voto per questa follia totale. Perché nel mondo esiste ReadOnlyObservableCollection anche se non puoi iscriverti agli eventi CollectionChanged? Qual è il punto? E a tutti quelli che continuano a dire che una raccolta di sola lettura non cambierà mai, pensa davvero a quello che stai dicendo.
MojoFilter

9
"sola lettura" non significa "immutabile" come alcuni sembrano pensare. Significa solo che il codice che può vedere la raccolta solo attraverso una tale proprietà non è autorizzato a modificarla. Può infatti essere modificato quando il codice aggiunge o rimuove membri tramite la raccolta sottostante. I lettori dovrebbero comunque essere in grado di essere informati che sono state apportate modifiche, anche se non possono modificare la raccolta da soli. Potrebbero comunque dover rispondere autonomamente ai cambiamenti. Non riesco a pensare a nessun motivo valido per limitare la proprietà CollectionChanged come è stato fatto in questo caso.
Gil

Risposte:



16

Ho trovato un modo per te su come farlo:

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

Devi solo fare riferimento alla tua raccolta in modo esplicito tramite l' interfaccia INotifyCollectionChanged .


14
Si prega di notare che ReadOnlyObservableCollection è un wrapper attorno a un ObservableCollection, che cambierà. I consumatori della ReadOnlyObservableCollection possono solo osservare questi cambiamenti, non cambiare nulla da soli.
Oskar

Non dovresti fare affidamento su questo fatto, la raccolta di sola lettura non cambierà in ogni caso, perché si chiama "sola lettura". Questa è la mia comprensione.
Restuta

4
+1 per la soluzione di colata. Ma quanto al fatto che non è logico osservare una raccolta di sola lettura: se avessi accesso in sola lettura a un database ti aspetteresti che non cambierà mai?
djskinner

17
Non vedo quale sia la difficoltà qui. ReadOnlyObservableCollection è una classe che fornisce un modo di sola lettura per osservare una raccolta. Se la mia classe ha una raccolta di, ad esempio, sessioni e non voglio che le persone siano in grado di aggiungere o rimuovere sessioni dalla mia raccolta, ma comunque di osservarle, allora ReadOnlyObservableCollection è il veicolo perfetto per questo. La raccolta è in lettura solo per gli utenti della mia classe, ma ne ho una copia in lettura / scrittura per uso personale.
scwagner

1
Navigando il codice .Net di ReadOnlyObservableCollectiontroverete questo: event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged. Implementazione esplicita dell'interfaccia dell'evento.
Mike de Klerk

7

So che questo post è vecchio, tuttavia, le persone dovrebbero dedicare del tempo per comprendere i modelli utilizzati in .NET prima di commentare. Una raccolta di sola lettura è un wrapper su una raccolta esistente che impedisce ai consumatori di modificarla direttamente, guarda ReadOnlyCollectione vedrai che è un wrapper su una IList<T>che può o non può essere modificabile. Le collezioni immutabili sono una questione diversa e sono coperte dalla nuova libreria delle collezioni immutabili

In altre parole, la sola lettura non è la stessa di immutabile !!!!

A parte questo, ReadOnlyObservableCollectiondovrebbe implicitamente implementare INotifyCollectionChanged.


5

Ci sono sicuramente buoni motivi per voler iscriversi alle notifiche di modifiche alla raccolta su ReadOnlyObservableCollection . Quindi, in alternativa al semplice cast della tua raccolta come INotifyCollectionChanged , se ti capita di creare una sottoclasse ReadOnlyObservableCollection , quanto segue fornisce un modo più sintatticamente conveniente per accedere all'evento a 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; }
    }
}

Questo ha funzionato bene per me prima.


5

Potresti votare per la voce di bug su Microsoft Connect che descrive questo problema: https://connect.microsoft.com/VisualStudio/feedback/details/641395/readonlyobservablecollection-t-collectionchanged-event-should-be-public

Aggiornare:

Il portale Connect è stato chiuso da Microsoft. Quindi il collegamento sopra non funziona più.

La mia libreria Win Application Framework (WAF) fornisce una soluzione: ReadOnlyObservableList classe:

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 è stato ritirato. Avrei votato totalmente
findusl

1

Come già risposto, hai due opzioni: puoi eseguire il cast ReadOnlyObservableCollection<T>dell'interfaccia INotifyCollectionChangedper accedere CollectionChangedall'evento implementato in modo esplicito , oppure puoi creare la tua classe wrapper che lo fa una volta nel costruttore e aggancia semplicemente gli eventi del wrapper ReadOnlyObservableCollection<T>.

Alcuni approfondimenti aggiuntivi sul motivo per cui questo problema non è stato ancora risolto:

Come puoi vedere dal codice sorgente , ReadOnlyObservableCollection<T>è una classe pubblica, non sigillata (cioè ereditabile), in cui gli eventi sono contrassegnati protected virtual.

Cioè, potrebbero esserci programmi compilati con classi derivate da ReadOnlyObservableCollection<T>, con definizioni di eventi sovrascritte ma protectedvisibilità. Quei programmi conterrebbero codice non valido una volta che la visibilità dell'evento viene modificata publicnella classe base, poiché non è consentito limitare la visibilità di un evento nelle classi derivate.

Quindi, sfortunatamente, creare protected virtualeventi in un publicsecondo momento è un cambiamento binario, e quindi non sarà fatto senza un ottimo ragionamento, cosa che temo "devo lanciare l'oggetto una volta per collegare i gestori" semplicemente non lo è.

Fonte: commento GitHub di Nick Guererra, 19 agosto 2015


0

Questo è stato il successo su Google, quindi ho pensato di aggiungere la mia soluzione nel caso in cui altre persone lo cercassero.

Utilizzando le informazioni di cui sopra (sulla necessità di eseguire il cast su INotifyCollectionChanged ), ho creato due metodi di estensione per la registrazione e l' annullamento della registrazione.

La mia soluzione - Metodi di estensione

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

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

Esempio

IThing.cs

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

Utilizzo dei metodi di estensione

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

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

Soluzione di 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;
}

Alternativa 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

Soluzione

ReadOnlyObservableCollection.CollectionChanged non è esposto (per validi motivi delineati in altre risposte), quindi creiamo la nostra classe wrapper che lo esponga:

/// <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);
}

Spiegazione

Le persone hanno chiesto perché vorresti osservare le modifiche a una raccolta di sola lettura, quindi spiegherò una delle tante situazioni valide; quando la raccolta di sola lettura racchiude una raccolta interna privata che può cambiare.

Ecco uno di questi scenari:

Supponiamo di avere un servizio che consente di aggiungere e rimuovere elementi da una raccolta interna dall'esterno del servizio. Si supponga ora di voler esporre i valori della raccolta ma non si desidera che i consumatori manipolino direttamente la raccolta; quindi avvolgi la raccolta interna in un file ReadOnlyObservableCollection.

Si noti che per racchiudere la raccolta interna con ReadOnlyObservableCollectionla raccolta interna è costretto a derivare da ObservableCollectiondal costruttore di ReadOnlyObservableCollection.

Supponiamo ora di voler avvisare i consumatori del servizio quando la raccolta interna cambia (e quindi quando ReadOnlyObservableCollectioncambia la parte esposta ). Piuttosto che lanciare la tua implementazione, vuoi solo esporre il CollectionChangeddi ReadOnlyObservableCollection. Piuttosto che costringere il consumatore a fare un'ipotesi sull'implementazione del ReadOnlyObservableCollection, scambia semplicemente il ReadOnlyObservableCollectioncon questa consuetudine ObservableReadOnlyCollectione il gioco è fatto.

Si ObservableReadOnlyCollectionnasconde ReadOnlyObservableCollection.CollectionChangedcon i propri e passa semplicemente tutti gli eventi modificati della raccolta a qualsiasi gestore di eventi associato.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.