¿Por qué ReadOnlyObservableCollection.CollectionChanged no es público?

86

¿Por qué está ReadOnlyObservableCollection.CollectionChangedprotegido y no es público (como lo ObservableCollection.CollectionChangedes el correspondiente )?

¿De qué sirve implementar una colección INotifyCollectionChangedsi no puedo acceder al CollectionChangedevento?

Oskar
fuente
1
Por curiosidad, ¿por qué estar esperando una colección de sólo lectura para el cambio? ¿Seguramente entonces no sería de solo lectura?
workmad3
83
Contrapregunta: ¿Por qué no esperaría que cambie una colección observable? ¿De qué sirve observarlo si nada va a cambiar? Bueno, la colección definitivamente va a cambiar, pero tengo consumidores a los que solo se les permite observarla. Mirando pero sin tocar ...
Oskar
1
Recientemente también me encontré con este problema. Básicamente, ObservableCollection no está implementando correctamente el evento modificado INotifyCollection. ¿Por qué C # permite que una clase restrinja los eventos de la interfaz de acceso pero no los métodos de la interfaz?
djskinner
35
Tengo que votar porque esto es una locura total. ¿Por qué en el mundo existe ReadOnlyObservableCollection si no puede suscribirse a los eventos CollectionChanged? WTF es el punto? Y para todos los que siguen diciendo que una colección de solo lectura nunca cambiará, piensen muy bien sobre lo que están diciendo.
MojoFilter
9
"sólo lectura" no significa "inmutable" como algunos parecen pensar. Solo significa que el código que solo puede ver la colección a través de dicha propiedad no puede cambiarla. De hecho, se puede cambiar cuando el código agrega o elimina miembros a través de la colección subyacente. Los lectores deberían poder recibir notificaciones de que se han producido cambios, incluso si no pueden cambiar la colección por sí mismos. Es posible que aún necesiten responder a los cambios por sí mismos. No puedo pensar en ninguna razón válida para restringir la propiedad CollectionChanged como se ha hecho en este caso.
Gil

Respuestas:

16

Encontré una manera para ti de cómo hacer esto:

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

Solo necesita consultar su colección explícitamente mediante la interfaz INotifyCollectionChanged .

Restuta
fuente
14
Tenga en cuenta que ReadOnlyObservableCollection es una envoltura alrededor de ObservableCollection, que va a cambiar. Los consumidores de ReadOnlyObservableCollection solo pueden observar estos cambios, no cambiar nada ellos mismos.
Oskar
No debe confiar en este hecho, la colección de solo lectura no va a cambiar en ningún caso, porque se llama "solo lectura". Este es mi entendimiento.
Restuta
4
+1 para la solución de colada. Pero en cuanto a que no es lógico observar una colección de solo lectura: si tuviera acceso de solo lectura a una base de datos, ¿esperaría que nunca cambiara?
djskinner
16
No veo cuál es la dificultad aquí. ReadOnlyObservableCollection es una clase que proporciona una forma de SÓLO LECTURA para observar una colección. Si mi clase tiene una colección de, digamos, sesiones, y no quiero que la gente pueda agregar o quitar sesiones de mi colección, pero aún las observe, entonces ReadOnlyObservableCollection es el vehículo perfecto para eso. La colección es de solo lectura para los usuarios de mi clase, pero tengo una copia de lectura / escritura para mi propio uso.
scwagner
1
Ver el código .Net de ReadOnlyObservableCollectionse encuentra esta: event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged. Implementación de interfaz explícita del evento.
Mike de Klerk
7

Sé que esta publicación es antigua, sin embargo, las personas deberían tomarse su tiempo para comprender los patrones utilizados en .NET antes de comentar. Una colección de solo lectura es una envoltura de una colección existente que evita que los consumidores la modifiquen directamente, mire ReadOnlyCollectiony verá que es una envoltura de una colección que IList<T>puede ser mutable o no. Las colecciones inmutables son un asunto diferente y están cubiertas por la nueva biblioteca de colecciones inmutables

En otras palabras, solo lectura no es lo mismo que inmutable.

Aparte de eso, ReadOnlyObservableCollectiondebería implementar implícitamente INotifyCollectionChanged.

Jason joven
fuente
5

Definitivamente hay buenas razones para querer suscribirse a las notificaciones de cambios de colección en ReadOnlyObservableCollection . Entonces, como una alternativa a simplemente convertir su colección como INotifyCollectionChanged , si está subclasificando ReadOnlyObservableCollection , entonces lo siguiente proporciona una forma más conveniente sintácticamente para acceder al evento 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; }
    }
}

Esto me ha funcionado bien antes.

porcus
fuente
5

Puede votar por la entrada de error en Microsoft Connect que describe este problema: https://connect.microsoft.com/VisualStudio/feedback/details/641395/readonlyobservablecollection-t-collectionchanged-event-should-be-public

Actualizar:

Microsoft ha cerrado el portal Connect. Entonces el enlace de arriba ya no funciona.

Mi biblioteca Win Application Framework (WAF) proporciona una solución: clase 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
fuente
Connect se ha retirado. Habría votado totalmente
findusl
1

Como ya se respondió, tiene dos opciones: puede enviar el ReadOnlyObservableCollection<T>a la interfaz INotifyCollectionChangedpara acceder al CollectionChangedevento implementado explícitamente , o puede crear su propia clase contenedora que hace eso una vez en el constructor y simplemente conecta los eventos del empaquetado ReadOnlyObservableCollection<T>.

Algunas ideas adicionales sobre por qué este problema aún no se ha solucionado:

Como puede ver en el código fuente , ReadOnlyObservableCollection<T>es una clase pública, no sellada (es decir, heredable), donde se marcan los eventos protected virtual.

Es decir, puede haber programas compilados con clases derivadas ReadOnlyObservableCollection<T>, con definiciones de eventos anuladas pero protectedvisibilidad. Esos programas contendrían código inválido una vez que la visibilidad del evento se cambie apublic la visibilidad del evento en la clase base, porque no se permite restringir la visibilidad de un evento en clases derivadas.

Entonces, desafortunadamente, hacer protected virtualeventos publicmás adelante es un cambio de ruptura binaria y, por lo tanto, no se hará sin un muy buen razonamiento, que me temo "Tengo que lanzar el objeto una vez para adjuntar controladores" simplemente no lo es.

Fuente: Comentario de GitHub por Nick Guererra, 19 de agosto de 2015

LWChris
fuente
0

Este fue el mayor éxito en Google, así que pensé en agregar mi solución en caso de que otras personas busquen esto.

Usando la información anterior (sobre la necesidad de transmitir a INotifyCollectionChanged ), hice dos métodos de extensión para registrar y cancelar el registro.

Mi solución : métodos de extensión

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

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

Ejemplo

IThing.cs

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

Usando los métodos de extensión

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

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

Solución de 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;
}
Dan
fuente
0

Solución

ReadOnlyObservableCollection.CollectionChanged no está expuesto (por razones válidas descritas en otras respuestas), así que hagamos nuestra propia clase contenedora que lo exponga:

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

Explicación

La gente ha preguntado por qué querría observar cambios en una colección de solo lectura, así que explicaré una de las muchas situaciones válidas; cuando la colección de solo lectura envuelve una colección interna privada que puede cambiar.

Aquí hay uno de esos escenarios:

Suponga que tiene un servicio que permite agregar y eliminar elementos a una colección interna desde fuera del servicio. Ahora suponga que desea exponer los valores de la colección pero no quiere que los consumidores manipulen la colección directamente; por lo que envuelve la colección interna en un ReadOnlyObservableCollection.

Tenga en cuenta que para envolver la colección interna con ReadOnlyObservableCollectionla colección interna se ve obligado a derivar de ObservableCollectionpor el constructor de ReadOnlyObservableCollection.

Ahora suponga que desea notificar a los consumidores del servicio cuando cambia la colección interna (y, por lo tanto, cuando ReadOnlyObservableCollectioncambia la expuesta ). En lugar de lanzar su propia implementación, solo desea exponer CollectionChangedel ReadOnlyObservableCollection. En lugar de obligar al consumidor a hacer una suposición sobre la implementación del ReadOnlyObservableCollection, simplemente intercambia el ReadOnlyObservableCollectioncon este personalizadoObservableReadOnlyCollection y listo.

Se ObservableReadOnlyCollectionoculta ReadOnlyObservableCollection.CollectionChangedcon los suyos y simplemente pasa todos los eventos modificados de la colección a cualquier controlador de eventos adjunto.

Zodman
fuente