Im Februar 2019 wurde ReactiveUI 9 veröffentlicht - das plattformübergreifende Framework zum Erstellen von GUI-Anwendungen auf der Microsoft .NET-Plattform. ReactiveUI ist ein Tool zur engen Integration reaktiver Erweiterungen in das MVVM-Entwurfsmuster. Sie können sich über eine Reihe von Videos oder die Begrüßungsseite der Dokumentation mit dem Framework vertraut machen. Das ReactiveUI 9-Update enthält zahlreiche Korrekturen und Verbesserungen . Das wahrscheinlich wichtigste und interessanteste ist jedoch die Integration in das DynamicData-Framework , mit dem Sie auf reaktive Weise mit dynamischen Sammlungen arbeiten können. Lassen Sie uns herausfinden, wofür wir DynamicData verwenden können und wie dieses leistungsstarke reaktive Framework unter der Haube funktioniert!
Einführung
Lassen Sie uns zunächst die Anwendungsfälle für DynamicData ermitteln und herausfinden, was uns an den Standardwerkzeugen für die Arbeit mit dynamischen Datasets aus dem System.Collections.ObjectModel
Namespace nicht gefällt.
Wie wir wissen, übernimmt die MVVM-Vorlage die Aufteilung der Verantwortung zwischen der Modellebene, der Präsentationsebene und dem App-Präsentationsmodell, auch als Ansichtsmodell bezeichnet. Die Modellebene wird durch Domänenentitäten und -dienste dargestellt und weiß nichts über die Ansichtsmodellebene. Das Modell kapselt die gesamte komplexe Logik der App, während das Ansichtsmodell Operationen an das Modell delegiert und den Zugriff auf Informationen zum aktuellen Status der App über beobachtbare Eigenschaften, Befehle und Sammlungen an die Ansicht ermöglicht. Das Standardwerkzeug für die Arbeit mit dynamischen Eigenschaften ist die INotifyPropertyChanged
Schnittstelle, für die Arbeit mit Benutzeraktionen - ICommand
und für die Arbeit mit Sammlungen - die INotifyCollectionChanged
Schnittstelle sowie für solche Implementierungen wie ObservableCollection<T>
und ReadOnlyObservableCollection<T>
.

Die Implementierung der INotifyPropertyChanged
und der ICommand
Schnittstelle ICommand
normalerweise vom Entwickler und dem verwendeten MVVM-Framework ab. Die Verwendung der Standard- ObservableCollection<T>
unterliegt jedoch einer Reihe von Einschränkungen! Beispielsweise können wir die Auflistung aus einem Hintergrundthread ohne Dispatcher.Invoke
oder einen ähnlichen Aufruf nicht mutieren. Dispatcher.Invoke
wäre sehr nützlich gewesen, um Datenarrays über eine Hintergrundoperation mit dem Server zu synchronisieren. Es ist anzumerken, dass die Modellschicht bei Verwendung der sauberen MVVM-Architektur nichts über das verwendete GUI-Framework wissen sollte und mit der Modellschicht in der MVC- oder MVP-Terminologie kompatibel sein sollte. Aus diesem Grund verstoßen diese zahlreichen Dispatcher.Invoke
Aufrufe in Domänendiensten gegen das Prinzip der Verantwortungssegregation.
Natürlich könnten wir ein Ereignis in einem Domänendienst deklarieren und einen Block mit geänderten Elementen als Ereignisargumente übertragen, dann das Ereignis abonnieren und den Dispatcher.Invoke
Aufruf hinter einer Schnittstelle kapseln, sodass unsere App nicht von einer GUI abhängt Rufen Sie diese Schnittstelle im Ansichtsmodell auf und ändern Sie ObservableCollection<T>
entsprechend. Es gibt jedoch eine viel elegantere Möglichkeit, mit solchen Problemen umzugehen, ohne das Rad neu erfinden zu müssen. Worauf warten wir dann noch?
Reaktive Erweiterungen. Verwalten beobachtbarer Datenströme
Um die von DynamicData eingeführten Abstraktionen und das Arbeiten mit sich ändernden reaktiven Datensätzen vollständig zu verstehen, erinnern wir uns, was reaktive Programmierung ist und wie sie im Kontext der Microsoft .NET-Plattform und des MVVM-Entwurfsmusters verwendet wird . Die Organisation der Interaktion zwischen Programmkomponenten kann interaktiv oder reaktiv sein. Beim interaktiven Ansatz empfängt der Verbraucher Daten synchron vom Produzenten (Pull-basiert, T, IEnumerable), und beim reaktiven Ansatz sendet der Produzent Daten asynchron an den Verbraucher (Push-basiert, Task, IObservable).

Reaktive Programmierung ist das Programmieren mit asynchronen Datenströmen, und reaktive Erweiterungen sind die reaktive Programmierimplementierung, die auf den Schnittstellen IObservable
und IObserver
aus dem System-Namespace IObservable
und eine Reihe von LINQ-ähnlichen Operationen auf der IObservable
Schnittstelle definiert, die als LINQ over Observable bezeichnet wird. Reaktive Erweiterungen unterstützen .NET Standard und werden überall dort ausgeführt, wo Microsoft .NET ausgeführt wird.

ReactiveUI bietet Anwendungsentwicklern die ICommand
, die reaktiven Implementierungen für die Schnittstellen ICommand
und INotifyPropertyChanged
zu nutzen, indem Tools wie ReactiveCommand<TIn, TOut>
und WhenAnyValue
. WhenAnyValue
Sie mit WhenAnyValue
eine Eigenschaft einer Klasse konvertieren können, die die INotifyPropertyChanged
Schnittstelle in einen Ereignisstrom vom Typ IObservable<T>
implementiert, vereinfacht dies die Implementierung abhängiger Eigenschaften.
public class ExampleViewModel : ReactiveObject { [Reactive]
ReactiveCommand<TIn, TOut>
können Sie mit einem Befehl wie mit IObservable<TOut>
, der veröffentlicht wird, wenn ein Befehl die Ausführung abschließt. Außerdem verfügt jeder Befehl über eine ThrownExceptions
Eigenschaft vom Typ IObservable<Exception>
.
Bis zu diesem Zeitpunkt haben wir mit IObservable<T>
, wie mit einem Ereignis, das einen neuen Wert vom Typ T
wenn sich der Status des beobachteten Objekts ändert. Einfach ausgedrückt ist IObservable<T>
ein Strom von Ereignissen, eine zeitlich gestreckte Sammlung vom Typ T
Natürlich können wir genauso einfach und natürlich mit Sammlungen arbeiten - wenn sich eine Sammlung ändert, können wir eine neue Sammlung mit geänderten Elementen veröffentlichen. In diesem Fall wäre der veröffentlichte Wert vom Typ IEnumerable<T>
oder spezialisierter, und der beobachtbare Stream selbst wäre vom Typ IObservable<IEnumerable<T>>
. Wie ein kritisch denkender Leser jedoch richtig feststellt, ist dies mit kritischen Leistungsproblemen behaftet, insbesondere wenn unsere Sammlung nicht ein Dutzend Elemente enthält, sondern hundert oder sogar einige Tausend!
Einführung in DynamicData
DynamicData ist eine Bibliothek, mit der Sie die Leistung reaktiver Erweiterungen bei der Arbeit mit Sammlungen nutzen können. Rx ist extrem leistungsfähig, bietet jedoch keine Unterstützung bei der Verwaltung von Sammlungen, und DynamicData behebt dies. In den meisten Anwendungen müssen Sammlungen dynamisch aktualisiert werden. In der Regel wird eine Sammlung beim Start der Anwendung mit Elementen gefüllt. Anschließend wird die Sammlung asynchron aktualisiert, um Informationen mit einem Server oder einer Datenbank zu synchronisieren. Moderne Anwendungen sind recht komplex und es ist häufig erforderlich, Projektionen von Sammlungen zu erstellen - Elemente filtern, transformieren oder sortieren. DynamicData wurde entwickelt, um den unglaublich komplexen Code zu beseitigen, den wir zur Verwaltung sich dynamisch ändernder Datensätze benötigen würden. Das Tool wird aktiv weiterentwickelt und verfeinert. Mittlerweile werden mehr als 60 Bediener für die Arbeit mit Sammlungen unterstützt.

DynamicData ist keine alternative Implementierung von ObservableCollection<T>
. Die Architektur von DynamicData basiert hauptsächlich auf domänengesteuerten Programmierkonzepten. Die Nutzungsideologie basiert auf der Tatsache, dass Sie eine bestimmte Datenquelle steuern, eine Sammlung, auf die der Code, der für die Synchronisierung und Mutation von Daten verantwortlich ist, Zugriff hat. Als Nächstes wenden Sie eine Reihe von Operatoren auf die Datenquelle an. Mithilfe dieser Operatoren können Sie die Daten deklarativ transformieren, ohne andere Sammlungen manuell erstellen und ändern zu müssen. Tatsächlich trennen Sie mit DynamicData Lese- und Schreibvorgänge und können nur reaktiv lesen. Daher bleiben die geerbten Sammlungen immer mit der Quelle synchronisiert.
Anstelle des klassischen IObservable<T>
definiert DynamicData Operationen für IObservable<IChangeSet<T>>
und IObservable<IChangeSet<TValue, TKey>>
, wobei IChangeSet
ein IChangeSet
ist, der Informationen über die Änderung der Sammlung einschließlich der Art der Änderung enthält und die betroffenen Elemente. Dieser Ansatz kann die Leistung des Codes für die Arbeit mit Sammlungen, die auf reaktive Weise geschrieben wurden, erheblich verbessern. Sie können IObservable<IChangeSet<T>>
IObservable<IEnumerable<T>>
in IObservable<IEnumerable<T>>
, wenn auf alle Elemente einer Sammlung IObservable<IEnumerable<T>>
muss. Wenn dies schwierig klingt - keine Sorge, die folgenden Codebeispiele machen alles klar!
Dynamische Daten in Aktion
Schauen wir uns einige Beispiele an, um besser zu verstehen, wie DynamicData funktioniert, wie es sich von System unterscheidet. Reaktiv und welche Aufgaben gewöhnliche Entwickler von GUI-Software lösen können. Beginnen wir mit einem umfassenden Beispiel, das auf GitHub veröffentlicht wurde . In diesem Beispiel lautet die Datenquelle SourceCache<Trade, long>
enthält eine Sammlung von Transaktionen. Ziel ist es, nur aktive Transaktionen anzuzeigen, Modelle in Proxy-Objekte umzuwandeln und die Sammlung zu sortieren.
Im obigen Beispiel ändert sich beim Ändern von SourceCache
, der die Datenquelle darstellt, auch SourceCache
entsprechend. Gleichzeitig wird beim Entfernen von Elementen aus der Sammlung die Dispose
Methode aufgerufen. Die Sammlung wird immer nur über den GUI-Thread aktualisiert und bleibt sortiert und gefiltert. Cool, jetzt haben wir keinen Dispatcher.Invoke
Rufen Sie Anrufe auf und der Code ist einfach und lesbar!
Datenquellen. SourceList und SourceCache
DynamicData bietet zwei spezialisierte Sammlungen, die als veränderbare Datenquelle verwendet werden können. Diese Sammlungen sind SourceList<TObject>
und SourceCache<TObject, TKey>
. Es wird empfohlen, SourceCache
zu verwenden, wenn TObject
einen eindeutigen Schlüssel hat, andernfalls SourceList
. Diese Objekte bieten die für .NET-Entwickler bekannte API für die Sammlungsverwaltung - Methoden wie Add
, Remove
, Insert
. Verwenden Sie den Operator .Connect()
um Datenquellen in IObservable<IChangeSet<T>>
oder in IObservable<IChangeSet<T, TKey>>
.Connect()
. Wenn Sie beispielsweise über einen Dienst verfügen, der eine Sammlung von Elementen im Hintergrund regelmäßig aktualisiert, können Sie die Liste dieser Elemente ohne Dispatcher.Invoke
und ähnlichen Code auf einfache Weise mit der GUI synchronisieren:
public class BackgroundService : IBackgroundService {
Mithilfe der leistungsstarken DynamicData-Operatoren können wir IObservable<IChangeSet<Trade>>
in IObservable<IChangeSet<Trade>>
, die in unserem Ansichtsmodell deklariert ist.
public class TradesViewModel : ReactiveObject { private readonly ReadOnlyObservableCollection<TradeVm> _trades; public ReadOnlyObservableCollection<TradeVm> Trades => _trades; public TradesViewModel(IBackgroundService background) {
Zusätzlich zu den Operatoren Transform
, Filter
und Sort
unterstützt DynamicData Gruppierung, logische Operationen, Sammlungsreduzierung, Verwendung von Aggregatfunktionen, Eliminierung identischer Elemente, Elementzählung und sogar Virtualisierung auf Ansichtsmodellebene. Weitere Informationen zu allen Operatoren finden Sie in der README-Datei des Projekts auf GitHub .

Abgesehen von SourceList
und SourceCache
die DynamicData-Bibliothek eine Single-Threaded-Implementierung für veränderbare Sammlungen - ObservableCollectionExtended
. Um zwei Sammlungen in Ihrem Ansichtsmodell zu synchronisieren, deklarieren Sie eine als ObservableCollectionExtended
und die andere als ReadOnlyObservableCollection
und verwenden Sie dann den Operator ToObservableChangeSet
, der fast dasselbe wie Connect ToObservableChangeSet
, jedoch mit ObservableCollection
.
DynamicData unterstützt auch die Änderungsverfolgung in Klassen, die die INotifyPropertyChanged
Schnittstelle implementieren. Wenn Sie beispielsweise bei jeder Änderung einer Eigenschaft Benachrichtigungen erhalten möchten, verwenden Sie den Operator AutoRefresh
und übergeben Sie die erforderliche Eigenschaftsauswahl. AutoRefresh
und anderen DynamicData-Operatoren können Sie mühelos eine große Anzahl von Formularen und verschachtelten Formularen überprüfen, die auf dem Bildschirm angezeigt werden!
Mit der DynamicData-Funktionalität können Sie komplexe Benutzeroberflächen erstellen. Dies ist besonders relevant für Systeme, die eine große Datenmenge in Echtzeit anzeigen, z. B. Instant Messaging-Apps und Überwachungssysteme.

Fazit
ReactiveX ist ein leistungsstarkes Tool, mit dem Sie mit Ereignisströmen und der Benutzeroberfläche arbeiten, tragbaren und wartbaren Code schreiben und komplexe Aufgaben auf einfache und elegante Weise lösen können. Mit ReactiveUI können .NET-Entwickler reaktive Erweiterungen mithilfe der MVVM-Architektur mit reaktiven Implementierungen von INotifyPropertyChanged
und ICommand
in ihre Projekte ICommand
, während DynamicData das Sammlungsmanagement durch die Implementierung von INotifyCollectionChanged
und die Funktionen reaktiver Erweiterungen mit Schwerpunkt auf Leistung erweitert.
ReactiveUI- und DynamicData-Bibliotheken sind vollständig kompatibel mit allen GUI-Frameworks auf der .NET-Plattform, einschließlich Windows Presentation Foundation, Universal Windows Platform, Avalonia , Xamarin.Android, Xamarin Forms und Xamarin iOS. Sie können DynamicData auf der entsprechenden Seite der ReactiveUI-Dokumentation studieren. Machen Sie sich auch mit dem DynamicData Snippets- Projekt vertraut, das Codebeispiele für fast alles enthält, was Sie möglicherweise benötigen.