Dlaczego ReadOnlyObservableCollection.CollectionChanged nie jest publiczny?

86

Dlaczego jest ReadOnlyObservableCollection.CollectionChangedchroniony, a nie publiczny (tak jak odpowiada ObservableCollection.CollectionChanged)?

Jaki jest pożytek z implementacji kolekcji, INotifyCollectionChangedjeśli nie mogę uzyskać dostępu do CollectionChangedwydarzenia?

Oskar
źródło
1
Z ciekawości, dlaczego miałbyś oczekiwać zmiany kolekcji tylko do odczytu ? Na pewno wtedy nie byłby tylko do odczytu?
workmad3
83
Kontr-pytanie: dlaczego nie spodziewałbym się, że kolekcja ObservableCollection ulegnie zmianie? Jaki pożytek z obserwowania tego, jeśli nic się nie zmieni? Cóż, kolekcja na pewno się zmieni, ale mam konsumentów, którym wolno ją tylko obserwować. Patrząc, ale nie dotykając ...
Oskar
1
Niedawno natknąłem się również na ten problem. Zasadniczo ObservableCollection nie implementuje poprawnie zdarzenia INotifyCollection modified. Dlaczego jest tak, że C # pozwala klasie ograniczyć dostęp do zdarzeń interfejsu, ale nie metod interfejsu?
djskinner
35
Muszę zagłosować za tym całkowitym szaleństwem. Dlaczego na świecie istnieje ReadOnlyObservableCollection, jeśli nie możesz subskrybować wydarzeń CollectionChanged? WTF o to chodzi? A każdemu, kto powtarza, że ​​kolekcja tylko do odczytu nigdy się nie zmieni, pomyśl naprawdę intensywnie o tym, co mówisz.
MojoFilter
9
„tylko do odczytu” nie oznacza „niezmiennego”, jak niektórym się wydaje. Oznacza to tylko, że kod, który może przeglądać kolekcję tylko przez taką właściwość, nie może jej zmienić. W rzeczywistości można to zmienić, gdy kod dodaje lub usuwa członków za pośrednictwem podstawowej kolekcji. Czytelnicy powinni nadal być powiadamiani o zmianach, nawet jeśli sami nie mogą zmienić zbioru. Być może nadal będą musieli sami zareagować na zmiany. Nie przychodzi mi do głowy żaden ważny powód, aby ograniczyć właściwość CollectionChanged, tak jak zostało to zrobione w tym przypadku.
Gil

Odpowiedzi:

16

Znalazłem dla Ciebie sposób, jak to zrobić:

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

Musisz tylko jawnie odwołać się do swojej kolekcji przez interfejs INotifyCollectionChanged .

Restuta
źródło
14
Proszę nie, że ReadOnlyObservableCollection jest opakowaniem otaczającym ObservableCollection - co ma się zmienić. Konsumenci ReadOnlyObservableCollection mogą tylko obserwować te zmiany, a nie zmieniać niczego samodzielnie.
Oskar
Nie powinieneś polegać na tym fakcie, zbiór tylko do odczytu nie zmieni się w żadnym przypadku, ponieważ nazywa się go „tylko do odczytu”. To jest moje zrozumienie.
Restuta
4
+1 za rozwiązanie do odlewania. Ale jeśli chodzi o obserwowanie zbioru tylko do odczytu, nie byłoby logiczne: gdybyś miał dostęp do bazy danych tylko do odczytu, czy spodziewałbyś się, że nigdy się nie zmieni?
djskinner
16
Nie rozumiem, na czym polega trudność. ReadOnlyObservableCollection to klasa, która zapewnia sposób TYLKO DO ODCZYTU obserwowania kolekcji. Jeśli moja klasa ma zbiór, powiedzmy, sesji i nie chcę, aby ludzie mogli dodawać lub usuwać sesje z mojej kolekcji, ale nadal je obserwują, to ReadOnlyObservableCollection jest do tego idealnym narzędziem. Kolekcja jest przeznaczona tylko do odczytu dla użytkowników mojej klasy, ale mam kopię do odczytu / zapisu na własny użytek.
scwagner
1
Przeglądając kod .NET ReadOnlyObservableCollectionznajdziecie poniżej: event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged. Jawna implementacja interfejsu zdarzenia.
Mike de Klerk
7

Wiem, że ten post jest stary, jednak ludzie powinni poświęcić trochę czasu na zrozumienie wzorców używanych w .NET przed skomentowaniem. Kolekcja tylko do odczytu jest opakowaniem istniejącej kolekcji, która uniemożliwia konsumentom jej bezpośrednią modyfikację, spójrz ReadOnlyCollectioni zobaczysz, że jest to opakowanie, IList<T>które może być modyfikowalne lub nie. Niezmienne kolekcje to inna sprawa i są objęte nową biblioteką niezmiennych kolekcji

Innymi słowy, tylko do odczytu to nie to samo, co niezmienne !!!!

Pomijając to, ReadOnlyObservableCollectionpowinno się domyślnie implementować INotifyCollectionChanged.

Jason Young
źródło
5

Z pewnością istnieją dobre powody, dla których warto subskrybować powiadomienia o zmianach kolekcji w ReadOnlyObservableCollection . Tak więc, jako alternatywa dla zwykłego rzutowania kolekcji jako INotifyCollectionChanged , jeśli zdarzy się, że tworzysz podklasę ReadOnlyObservableCollection , poniższy sposób zapewnia bardziej wygodny składniowo sposób uzyskania dostępu do zdarzenia 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; }
    }
}

Wcześniej dobrze mi to działało.

porcus
źródło
5

Możesz głosować na wpis błędu w Microsoft Connect, który opisuje ten problem: https://connect.microsoft.com/VisualStudio/feedback/details/641395/readonlyobservablecollection-t-collectionchanged-event-should-be-public

Aktualizacja:

Portal Connect został zamknięty przez firmę Microsoft. Więc powyższy link już nie działa.

Biblioteka My Win Application Framework (WAF) zapewnia rozwiązanie: ReadOnlyObservableList class:

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
źródło
Connect został wycofany. Całkowicie zagłosowałbym
findusl
1

Jak już odpowiedzieliśmy, masz dwie opcje: możesz albo rzucić na ReadOnlyObservableCollection<T>interfejs, INotifyCollectionChangedaby uzyskać dostęp do jawnie zaimplementowanego CollectionChangedzdarzenia, albo możesz stworzyć własną klasę opakowującą, która robi to raz w konstruktorze i po prostu podłącza zdarzenia opakowanegoReadOnlyObservableCollection<T> .

Dodatkowe informacje, dlaczego ten problem nie został jeszcze rozwiązany:

Jak widać z kodu źródłowego , ReadOnlyObservableCollection<T>jest to publiczna, niezamykana (czyli dziedziczona) klasa, w której zdarzenia są oznaczoneprotected virtual .

Oznacza to, że mogą istnieć skompilowane programy z klasami, z których pochodzą ReadOnlyObservableCollection<T>, z nadpisanymi definicjami zdarzeń, ale z protectedwidocznością. Te programy będą zawierały nieprawidłowy kod, gdy widoczność zdarzenia zostanie zmieniona napublic w klasie bazowej, ponieważ nie można ograniczać widoczności zdarzenia w klasach pochodnych.

Tak więc, niestety, późniejsze tworzenie protected virtualzdarzeń publicjest binarną zmianą, a zatem nie zostanie to zrobione bez bardzo dobrego uzasadnienia, którego obawiam się, że „muszę raz rzucić obiekt, aby dołączyć handlery” po prostu nie jest.

Źródło: komentarz GitHub, Nick Guererra, 19 sierpnia 2015

LWChris
źródło
0

To był hit w Google, więc pomyślałem, że dodam moje rozwiązanie na wypadek, gdyby inni to sprawdzili.

Korzystając z powyższych informacji (o konieczności rzutowania na INotifyCollectionChanged ), utworzyłem dwie metody rozszerzające, aby zarejestrować i wyrejestrować.

Moje rozwiązanie - metody rozszerzeń

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

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

Przykład

IThing.cs

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

Korzystanie z metod rozszerzających

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

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

Rozwiązanie 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;
}

Alternatywa 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
źródło
0

Rozwiązanie

ReadOnlyObservableCollection.CollectionChanged nie jest ujawniona (z ważnych powodów opisanych w innych odpowiedziach), więc stwórzmy własną klasę opakowującą, która ją ujawnia:

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

Wyjaśnienie

Ludzie pytają, dlaczego chciałbyś obserwować zmiany w zbiorze tylko do odczytu, więc wyjaśnię jedną z wielu ważnych sytuacji; gdy kolekcja tylko do odczytu otacza prywatną kolekcję wewnętrzną, która może ulec zmianie.

Oto jeden taki scenariusz:

Załóżmy, że masz usługę, która umożliwia dodawanie i usuwanie elementów do wewnętrznej kolekcji spoza usługi. Teraz załóżmy, że chcesz ujawnić wartości kolekcji, ale nie chcesz, aby konsumenci bezpośrednio manipulowali kolekcją; więc opakowujesz wewnętrzną kolekcję w plik ReadOnlyObservableCollection.

Zauważ, że aby opakować kolekcję wewnętrzną z ReadOnlyObservableCollectionkolekcją wewnętrzną, jest ona zmuszona ObservableCollectionprzez konstruktoraReadOnlyObservableCollection .

Teraz przypuśćmy, że chcesz powiadomić konsumentów o usłudze, gdy zmieni się kolekcja wewnętrzna (a tym samym kiedy ujawnione ReadOnlyObservableCollectionzmiany). Zamiast toczenia własną implementację chcesz tylko wystawiać CollectionChangedz ReadOnlyObservableCollection. Zamiast zmuszać konsumenta do przyjęcia założenia na temat implementacji ReadOnlyObservableCollection, wystarczy zamienić rozszerzenieReadOnlyObservableCollection ten zwyczaj ObservableReadOnlyCollectioni gotowe.

Do ObservableReadOnlyCollectionskóry ReadOnlyObservableCollection.CollectionChangedz jego rękę, a po prostu przechodzi na wszystkich zbiórki zmienionych zdarzeń do wszelkich załączonych obsługi zdarzeń.

Zodman
źródło