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?


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 .


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.


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.


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

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


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

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.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.