Por que ReadOnlyObservableCollection.CollectionChanged não é público?

86

Por que é ReadOnlyObservableCollection.CollectionChangedprotegido e não público (como o correspondente ObservableCollection.CollectionChanged)?

Qual é a utilidade de uma implementação de coleção INotifyCollectionChangedse eu não consigo acessar o CollectionChangedevento?

Oskar
fonte
1
Por curiosidade, por que você espera que uma coleção somente leitura seja alterada? Certamente então não seria somente leitura?
workmad3 de
83
Contra-pergunta: Por que eu não esperaria que uma ObservableCollection mudasse? Qual é a utilidade de observá-lo se nada vai mudar? Bem, a coleção definitivamente vai mudar, mas tenho consumidores que só podem observar isso. Olhando, mas sem tocar ...
Oskar
1
Recentemente me deparei com esse problema também. Basicamente, ObservableCollection não está implementando o evento alterado INotifyCollection corretamente. Por que o C # permite que uma classe restrinja eventos de interface de acesso, mas não métodos de interface?
djskinner
35
Eu tenho que dar meu voto por isso ser uma loucura total. Por que o ReadOnlyObservableCollection ainda existe se você não pode se inscrever em eventos CollectionChanged? WTF é o ponto? E para todos que continuam dizendo que uma coleção somente leitura nunca mudará, pense muito bem sobre o que você está dizendo.
MojoFilter
9
"somente leitura" não significa "imutável" como alguns parecem pensar. Significa apenas que o código que só pode ver a coleção por meio de tal propriedade não tem permissão para alterá-la. Na verdade, ele pode ser alterado quando o código adiciona ou remove membros por meio da coleção subjacente. Os leitores ainda devem ser notificados de que ocorreram alterações, mesmo que eles próprios não possam alterar a coleção. Eles ainda podem precisar responder às mudanças por si próprios. Não consigo pensar em nenhuma razão válida para restringir a propriedade CollectionChanged como foi feito neste caso.
Gil

Respostas:

16

Eu encontrei uma maneira de como fazer isso:

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

Você só precisa se referir à sua coleção explicitamente pela interface INotifyCollectionChanged .

Restuta
fonte
14
Observe que ReadOnlyObservableCollection é um invólucro em torno de ObservableCollection - que vai mudar. Os consumidores de ReadOnlyObservableCollection têm permissão apenas para observar essas alterações, não alterar nada eles próprios.
Oskar
Você não deve confiar neste fato, a coleção somente leitura não vai mudar em nenhum caso, porque é chamada de "somente leitura". Este é o meu entendimento.
Restuta
4
1 para a solução de fundição. Mas, quanto a não ser lógico observar uma coleção somente leitura: se você tivesse acesso somente leitura a um banco de dados, esperaria que ele nunca mudasse?
djskinner
16
Não vejo qual é a dificuldade aqui. O ReadOnlyObservableCollection é uma classe que fornece uma maneira SOMENTE DE LEITURA de observar uma coleção. Se minha classe tem uma coleção de, digamos, sessões, e eu não quero que as pessoas possam adicionar ou remover sessões de minha coleção, mas ainda assim observá-las, ReadOnlyObservableCollection é o veículo perfeito para isso. A coleção é lida apenas para os usuários de minha classe, mas eu tenho uma cópia de leitura / gravação para meu próprio uso.
scwagner
1
Navegando o código .Net de ReadOnlyObservableCollectionvocê vai encontrar este: event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged. Implementação de interface explícita do evento.
Mike de Klerk
7

Eu sei que este post é antigo, no entanto, as pessoas devem levar um tempo para entender os padrões usados ​​no .NET antes de comentar. Uma coleção somente leitura é um wrapper em uma coleção existente que impede os consumidores de modificá-la diretamente. Observe ReadOnlyCollectione você verá que é um wrapper em um IList<T>que pode ou não ser mutável. Coleções imutáveis ​​são um assunto diferente e são cobertas pela nova biblioteca de coleções imutáveis

Em outras palavras, somente leitura não é o mesmo que imutável !!!!

Deixando isso de lado, ReadOnlyObservableCollectiondeve implementar implicitamente INotifyCollectionChanged.

Jason Young
fonte
5

Definitivamente, existem boas razões para querer assinar notificações de alterações de coleção em um ReadOnlyObservableCollection . Portanto, como uma alternativa para meramente converter sua coleção como INotifyCollectionChanged , se você estiver subclassificando ReadOnlyObservableCollection , o seguinte fornece uma maneira mais sintaticamente conveniente de acessar o 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; }
    }
}

Isso funcionou bem para mim antes.

Porcus
fonte
5

Você pode votar na entrada do bug no Microsoft Connect que descreve esse problema: https://connect.microsoft.com/VisualStudio/feedback/details/641395/readonlyobservablecollection-t-collectionchanged-event-should-be-public

Atualizar:

O portal Connect foi encerrado pela Microsoft. Então o link acima não funciona mais.

A biblioteca My Win Application Framework (WAF) fornece uma solução: classe 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
fonte
O Connect foi desativado. Eu teria votado totalmente
findusl
1

Como já respondido, você tem duas opções: você pode lançar o ReadOnlyObservableCollection<T>para a interface INotifyCollectionChangedpara acessar o CollectionChangedevento explicitamente implementado ou pode criar sua própria classe de wrapper que faz isso uma vez no construtor e apenas conecta os eventos do wrapper ReadOnlyObservableCollection<T>.

Alguns insights adicionais sobre por que esse problema ainda não foi corrigido:

Como você pode ver no código-fonte , ReadOnlyObservableCollection<T>é uma classe pública, não lacrada (ou seja, herdável), onde os eventos são marcados protected virtual.

Ou seja, pode haver programas compilados com classes derivadas de ReadOnlyObservableCollection<T>, com definições de eventos substituídas, mas com protectedvisibilidade. Esses programas conteriam código inválido uma vez que a visibilidade do evento fosse alterada para publicna classe base, porque não é permitido restringir a visibilidade de um evento em classes derivadas.

Portanto, infelizmente, criar protected virtualeventos publicposteriormente é uma alteração de quebra de binário e, portanto, não será feito sem um raciocínio muito bom, o que, infelizmente, "tenho que lançar o objeto uma vez para anexar manipuladores" não é.

Fonte: comentário do GitHub por Nick Guererra, 19 de agosto de 2015

LWChris
fonte
0

Este foi o maior sucesso no Google, então decidi adicionar minha solução no caso de outras pessoas procurarem.

Usando as informações acima (sobre a necessidade de transmitir para INotifyCollectionChanged ), fiz dois métodos de extensão para registrar e cancelar o registro.

Minha solução - Métodos de extensão

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

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

Exemplo

IThing.cs

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

Usando os métodos de extensão

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

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

Solução 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
fonte
0

Solução

ReadOnlyObservableCollection.CollectionChanged não é exposto (por razões válidas descritas em outras respostas), então vamos fazer nossa própria classe de wrapper que o expõe:

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

Explicação

As pessoas perguntam por que você deseja observar as alterações em uma coleção somente leitura, então explicarei uma das muitas situações válidas; quando a coleção somente leitura envolve uma coleção interna privada que pode mudar.

Aqui está um desses cenários:

Suponha que você tenha um serviço que permite adicionar e remover itens de fora do serviço para uma coleção interna. Agora suponha que você deseja expor os valores da coleção, mas não deseja que os consumidores manipulem a coleção diretamente; então você envolve a coleção interna em a ReadOnlyObservableCollection.

Observe que para envolver a coleção interna com ReadOnlyObservableCollectiona coleção interna é forçado a derivar ObservableCollectionpelo construtor de ReadOnlyObservableCollection.

Agora, suponha que você deseja notificar os consumidores sobre o serviço quando a coleção interna muda (e, portanto, quando a exposição ReadOnlyObservableCollectionmuda). Em vez de lançar sua própria implementação, você apenas deseja expor o CollectionChangedde ReadOnlyObservableCollection. Em vez de forçar o consumidor a fazer uma suposição sobre a implementação do ReadOnlyObservableCollection, você simplesmente troca o ReadOnlyObservableCollectionpor este personalizado ObservableReadOnlyCollectione pronto.

O ObservableReadOnlyCollectionoculta ReadOnlyObservableCollection.CollectionChangedcom o seu próprio e simplesmente passa todos os eventos alterados da coleção para qualquer manipulador de eventos anexado.

Zodman
fonte