Dlaczego jest ReadOnlyObservableCollection.CollectionChanged
chroniony, a nie publiczny (tak jak odpowiada ObservableCollection.CollectionChanged
)?
Jaki jest pożytek z implementacji kolekcji, INotifyCollectionChanged
jeśli nie mogę uzyskać dostępu do CollectionChanged
wydarzenia?
c#
.net
collections
Oskar
źródło
źródło
Odpowiedzi:
Oto rozwiązanie: zdarzenia CollectionChanged w ReadOnlyObservableCollection
Trzeba rzucić kolekcję do INotifyCollectionChanged .
źródło
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 .
źródło
ReadOnlyObservableCollection
znajdziecie poniżej:event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged
. Jawna implementacja interfejsu zdarzenia.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
ReadOnlyCollection
i 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 kolekcjiInnymi słowy, tylko do odczytu to nie to samo, co niezmienne !!!!
Pomijając to,
ReadOnlyObservableCollection
powinno się domyślnie implementowaćINotifyCollectionChanged
.źródło
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.
źródło
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; } } }
źródło
Jak już odpowiedzieliśmy, masz dwie opcje: możesz albo rzucić na
ReadOnlyObservableCollection<T>
interfejs,INotifyCollectionChanged
aby uzyskać dostęp do jawnie zaimplementowanegoCollectionChanged
zdarzenia, 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 zprotected
widocznoś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 virtual
zdarzeńpublic
jest 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
źródło
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; }
źródło
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
.Teraz przypuśćmy, że chcesz powiadomić konsumentów o usłudze, gdy zmieni się kolekcja wewnętrzna (a tym samym kiedy ujawnione
ReadOnlyObservableCollection
zmiany). Zamiast toczenia własną implementację chcesz tylko wystawiaćCollectionChanged
zReadOnlyObservableCollection
. Zamiast zmuszać konsumenta do przyjęcia założenia na temat implementacjiReadOnlyObservableCollection
, wystarczy zamienić rozszerzenieReadOnlyObservableCollection
ten zwyczajObservableReadOnlyCollection
i gotowe.Do
ObservableReadOnlyCollection
skóryReadOnlyObservableCollection.CollectionChanged
z jego rękę, a po prostu przechodzi na wszystkich zbiórki zmienionych zdarzeń do wszelkich załączonych obsługi zdarzeń.źródło