Im Februar 2019 wurde
ReactiveUI 9 , ein plattformübergreifendes Framework zum Erstellen von GUI-Anwendungen auf der Microsoft .NET-Plattform, veröffentlicht.
ReactiveUI ist ein Tool zur engen Integration reaktiver Erweiterungen in das MVVM-Entwurfsmuster. Die Bekanntschaft mit dem Framework kann mit einer
Reihe von Artikeln über Habré oder
auf der Titelseite der Dokumentation begonnen werden . Das ReactiveUI 9-Update enthält viele
Korrekturen und Verbesserungen . Die vielleicht interessanteste und bedeutendste Änderung ist jedoch die
enge Integration in das DynamicData-Framework , das das Arbeiten mit sich ändernden Sammlungen in einem reaktiven Stil ermöglicht. Versuchen wir herauszufinden, in welchen Fällen
DynamicData nützlich sein kann und wie dieses leistungsstarke reaktive Framework im Inneren angeordnet ist!
Hintergrund
Zunächst definieren wir den Aufgabenbereich, den
DynamicData löst, und finden heraus, warum Standardtools für die Arbeit mit sich ändernden Datensätzen aus dem
System.Collections.ObjectModel
Namespace nicht zu uns passen.
Wie Sie wissen, umfasst die MVVM-Vorlage die Aufteilung der Verantwortung zwischen den Ebenen des Modells, der Präsentation und des Anwendungspräsentationsmodells. Die Modellebene wird durch Domänenentitäten und -dienste dargestellt und weiß nichts über das Präsentationsmodell. Die Modellschicht kapselt die gesamte komplexe Anwendungslogik, und das Präsentationsmodell delegiert die Operationen des Modells und gibt der Ansicht über beobachtbare Eigenschaften, Befehle und Sammlungen Zugriff auf Informationen zum aktuellen Status der Anwendung. Das Standardwerkzeug zum Arbeiten mit sich ändernden Eigenschaften ist die
INotifyPropertyChanged
Schnittstelle, der
INotifyPropertyChanged
zum Arbeiten mit Benutzeraktionen und
INotifyCollectionChanged
Implementieren von Sammlungen und zum Implementieren von
ObservableCollection
und
ReadOnlyObservableCollection
.

Die Implementierung von
INotifyPropertyChanged
und
ICommand
bleibt normalerweise dem Gewissen des Entwicklers und des verwendeten MVVM-Frameworks
ICommand
, aber die Verwendung von
ObservableCollection
einer Reihe von Einschränkungen für uns! Beispielsweise können wir eine Sammlung aus einem Hintergrundthread ohne
Dispatcher.Invoke
oder einen ähnlichen Aufruf nicht ändern. Dies kann hilfreich sein, wenn Sie mit Datenarrays arbeiten, die von einer Hintergrundoperation mit dem Server synchronisiert werden. Es ist zu beachten, dass in der idiomatischen MVVM die Modellschicht nicht über die Architektur der verwendeten GUI-Anwendung Bescheid wissen und mit dem Modell von MVC oder MVP kompatibel sein muss. Aus diesem Grund ermöglichen zahlreiche
Dispatcher.Invoke
den Zugriff auf die Benutzeroberfläche über den laufenden Hintergrundthread Verstoßen Sie in einem Domänendienst gegen das Prinzip der Aufteilung der Verantwortung zwischen den Anwendungsebenen.
Natürlich wäre es in einem Domänendienst möglich, ein Ereignis zu deklarieren und als Argument eines Ereignisses einen Block mit geänderten Daten zu übergeben. Abonnieren Sie dann das Ereignis, wickeln Sie den Aufruf von
Dispatcher.Invoke
in eine Schnittstelle ein, damit er nicht vom verwendeten GUI-Framework abhängt, verschieben Sie
Dispatcher.Invoke
in das Präsentationsmodell und ändern Sie die
ObservableCollection
Bedarf. Es gibt jedoch eine viel einfachere und elegantere Möglichkeit, den angegebenen Aufgabenbereich zu lösen, ohne ein Fahrrad schreiben zu müssen . Beginnen wir mit dem Studium!
Reaktive Erweiterungen. Datenströme verwalten
Um ein umfassendes Verständnis der von
DynamicData eingeführten
Abstraktionen und der Prinzipien der Arbeit mit sich ändernden reaktiven Datensätzen zu erhalten, erinnern wir uns daran,
was reaktive Programmierung ist und wie sie im Kontext der Microsoft .NET-Plattform und des MVVM-Entwurfsmusters angewendet wird . Eine Möglichkeit, die Interaktion zwischen Programmkomponenten zu organisieren, kann interaktiv und reaktiv sein. In der interaktiven Interaktion empfängt die Consumer-Funktion synchron Daten von der Provider-Funktion (Pull-basierter Ansatz,
T
,
IEnumerable
), und in der reaktiven Interaktion liefert die Consumer-Funktion asynchron Daten an die Consumer-Funktion (Push-basierter Ansatz,
Task
,
IObservable
).
Reaktive Programmierung ist die Programmierung mit asynchronen Datenströmen, und reaktive Erweiterungen sind ein Sonderfall ihrer Implementierung, basierend auf den
IObservable
und
IObserver
aus dem System-Namespace, der eine Reihe von LINQ-ähnlichen Operationen auf der
IObservable
Schnittstelle definiert, die als LINQ over Observable bezeichnet werden. Reaktive Erweiterungen unterstützen den .NET-Standard und funktionieren überall dort, wo die Microsoft .NET-Plattform funktioniert.

Das ReactiveUI-Framework lädt Anwendungsentwickler ein, die reaktive Implementierung von
ICommand
und
INotifyPropertyChanged
und leistungsstarke Tools wie
ReactiveCommand<TIn, TOut>
und
WhenAnyValue
.
WhenAnyValue
können
WhenAnyValue
eine Eigenschaft einer Klasse, die INotifyPropertyChanged implementiert, in einen Ereignisstrom vom Typ
IObservable<T>
, wodurch die Implementierung abhängiger Eigenschaften vereinfacht wird.
public class ExampleViewModel : ReactiveObject { [Reactive]
ReactiveCommand<TIn, TOut>
können Sie mit dem Befehl arbeiten, wie mit einem Ereignis vom Typ
IObservable<TOut>
, das veröffentlicht wird, wenn der Befehl die Ausführung abschließt. Außerdem verfügt jeder Befehl über eine
ThrownExceptions
Eigenschaft vom Typ
IObservable<Exception>
.
IObservable<T>
dieser ganzen Zeit haben wir mit
IObservable<T>
, wie bei einem Ereignis, das einen neuen Wert vom Typ
T
wenn sich der Status des überwachten Objekts ändert. Einfach ausgedrückt ist
IObservable<T>
ein Strom von Ereignissen, eine Sequenz, die sich über die Zeit erstreckt.
Natürlich können wir genauso einfach und natürlich mit Sammlungen arbeiten - wenn sich eine Sammlung ändert, veröffentlichen Sie eine neue Sammlung mit geänderten Elementen. In diesem Fall wäre der veröffentlichte Wert vom Typ
IEnumerable<T>
oder spezialisierter, und das Ereignis selbst wäre vom Typ
IObservable<IEnumerable<T>>
. Wie der kritisch denkende Leser jedoch richtig hervorhebt, ist dies mit ernsthaften Problemen bei der Anwendungsleistung behaftet, insbesondere wenn unsere Sammlung nicht ein Dutzend Elemente enthält, sondern hundert oder sogar mehrere Tausend!
Einführung in DynamicData
DynamicData ist eine Bibliothek, mit der Sie bei der Arbeit mit Sammlungen die volle Leistung reaktiver Erweiterungen nutzen können. Reaktive Erweiterungen bieten keine optimalen Möglichkeiten, um mit sich ändernden Datasets zu arbeiten, und
DynamicData hat die Aufgabe, diese zu beheben. In den meisten Anwendungsanwendungen müssen Sammlungen dynamisch aktualisiert werden. In der Regel wird eine Sammlung beim Start der Anwendung mit einigen Elementen gefüllt und dann asynchron aktualisiert, um Informationen mit einem Server oder einer Datenbank zu synchronisieren. Moderne Anwendungen sind recht komplex, und häufig müssen Sammlungsprojektionen erstellt werden - 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 entwickelt und finalisiert. Mittlerweile werden mehr als 60 Bediener für die Arbeit mit Sammlungen unterstützt.
DynamicData ist keine alternative Implementierung von
ObservableCollection<T>
. Die
DynamicData- Architektur basiert hauptsächlich auf den Konzepten der domänenspezifischen Programmierung. Die Nutzungsideologie basiert auf der Tatsache, dass Sie eine Datenquelle verwalten, eine Sammlung, auf die der Code, der für die Synchronisierung und Änderung von Daten verantwortlich ist, Zugriff hat. Als Nächstes wenden Sie eine Reihe von Operatoren auf die Quelle an, mit denen Sie die Daten deklarativ transformieren können, 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 werden geerbte 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 Sammlungsänderung enthält - die Art der Änderung und die betroffenen Elemente. Dieser Ansatz kann die Leistung von Code für die Arbeit mit Sammlungen, die in einem reaktiven Stil geschrieben wurden, erheblich verbessern. Gleichzeitig kann
IObservable<IChangeSet<T>>
immer in ein reguläres
IObservable<IEnumerable<T>>
wenn alle Elemente der Sammlung gleichzeitig verarbeitet werden müssen. Wenn es kompliziert klingt - seien Sie nicht beunruhigt, aus den Codebeispielen wird alles klar und transparent!
DynamicData-Beispiel
Schauen wir uns eine Reihe von Beispielen an, um besser zu verstehen, wie DynamicData funktioniert, wie es sich von
System.Reactive
und welche Aufgaben gewöhnliche Entwickler von Anwendungssoftware mit einer GUI lösen können. Beginnen wir mit einem umfassenden Beispiel,
das von DynamicData auf GitHub veröffentlicht wurde . Im Beispiel ist die Datenquelle
SourceCache<Trade, long>
, der eine Sammlung von Transaktionen enthält. Die Aufgabe besteht darin, nur aktive Transaktionen anzuzeigen, Modelle in Proxy-Objekte umzuwandeln und die Sammlung zu sortieren.
Wenn Sie im
SourceCache
Beispiel den
SourceCache
ändern, bei dem es sich um die
SourceCache
, ändert sich auch
SourceCache
entsprechend. In diesem Fall wird beim Löschen von Elementen aus der Auflistung die
Dispose
Methode aufgerufen. Die Auflistung wird immer nur im GUI-Stream aktualisiert und bleibt sortiert und gefiltert. Cool, kein
Dispatcher.Invoke
und komplizierter Code!
SourceList- und SourceCache-Datenquellen
DynamicData bietet zwei spezialisierte Sammlungen, die als veränderbare Datenquelle verwendet werden können. Diese Sammlungen sind vom Typ
SourceList
und
SourceCache<TObject, TKey>
. Es wird empfohlen,
SourceCache
zu verwenden, wenn
TObject
einen eindeutigen Schlüssel hat, andernfalls
SourceList
. Diese Objekte bieten die bekannte .NET-Entwickler-API zum Ändern von Daten -
Add
,
Remove
,
Insert
und dergleichen. Verwenden Sie den Operator
.Connect()
um Datenquellen in
IObservable<IChangeSet<T>>
oder
IObservable<IChangeSet<T, TKey>>
.Connect()
. Wenn Sie beispielsweise über einen Dienst verfügen, der die Sammlung von Elementen im Hintergrund aktualisiert, können Sie die Liste dieser Elemente problemlos mit der GUI synchronisieren, ohne
Dispatcher.Invoke
und architektonische Exzesse:
public class BackgroundService : IBackgroundService {
DynamicData verwendet integrierte .NET-Typen, um Daten der Außenwelt zuzuordnen. Mit den leistungsstarken DynamicData-Operatoren können wir
IObservable<IChangeSet<Trade>>
in
ReadOnlyObservableCollection
unseres Ansichtsmodells
IObservable<IChangeSet<Trade>>
.
public class TradesViewModel : ReactiveObject { private readonly ReadOnlyObservableCollection<TradeVm> _trades; public ReadOnlyObservableCollection<TradeVm> Trades => _trades; public TradesViewModel(IBackgroundService background) {
Neben
Transform
,
Filter
und
Sort
enthält DynamicData eine Vielzahl weiterer Operatoren, unterstützt Gruppierungen, logische Operationen, das Glätten einer Sammlung, die Verwendung von Aggregationsfunktionen, das Ausschließen identischer Elemente, das Zählen von Elementen und sogar die Virtualisierung auf der Ebene des Darstellungsmodells. Lesen Sie mehr über alle Operatoren im
README-Projekt auf GitHub .

Sammlungen mit einem Thread und Änderungsverfolgung
Neben
SourceList
und
SourceCache
die DynamicData-Bibliothek auch eine Single-Thread-Implementierung einer veränderlichen Sammlung -
ObservableCollectionExtended
. Um zwei Sammlungen in Ihrem Ansichtsmodell zu synchronisieren, deklarieren Sie eine als
ObservableCollectionExtended
und die andere als
ReadOnlyObservableCollection
und verwenden Sie den Operator
ToObservableChangeSet
, der sich wie
Connect
verhält, jedoch für die Arbeit mit
ObservableCollection
.
DynamicData unterstützt auch das Verfolgen von Änderungen in Klassen, die die
INotifyPropertyChanged
Schnittstelle implementieren. Wenn Sie beispielsweise über eine Auflistungsänderung benachrichtigt werden möchten, wenn sich eine Eigenschaft eines Elements ändert, verwenden Sie die
AutoRefresh
und übergeben Sie den Selektor der gewünschten Eigenschaft mit dem Argument.
AutoRefesh
und anderen DynamicData-Operatoren können Sie die große Anzahl von Formularen und Unterformularen, die auf dem Bildschirm angezeigt werden, einfach und natürlich überprüfen!
Basierend auf der DynamicData-Funktionalität können Sie schnell recht komplexe Schnittstellen erstellen. Dies gilt insbesondere für Systeme, die eine große Menge von Echtzeitdaten anzeigen, Instant Messaging-Systeme und Überwachungssysteme.

Fazit
Reaktive Erweiterungen sind ein leistungsstarkes Tool, mit dem Sie deklarativ mit Daten und der Benutzeroberfläche arbeiten, tragbaren und unterstützten Code schreiben und komplexe Probleme auf einfache und elegante Weise lösen können.
Mit ReactiveUI können .NET-Entwickler mithilfe der MVVM-Architektur reaktive Erweiterungen eng in ihre Projekte integrieren, indem sie reaktive Implementierungen von
INotifyPropertyChanged
und
ICommand
.
DynamicData kümmert sich um die Synchronisierung der Sammlung, indem
INotifyCollectionChanged
implementiert
INotifyCollectionChanged
, wodurch die Funktionen reaktiver Erweiterungen erweitert und die Leistung
INotifyCollectionChanged
.
Die
ReactiveUI- und
DynamicData-Bibliotheken sind mit den gängigsten GUI-Frameworks der .NET-Plattform kompatibel, einschließlich Windows Presentation Foundation, Universal Windows Platform,
Avalonia , Xamarin.Android, Xamarin Forms und Xamarin.iOS. Sie können DynamicData auf der
entsprechenden ReactiveUI-Dokumentationsseite lernen.
Schauen Sie sich auch das
DynamicData Snippets- Projekt an, das Beispiele für die Verwendung von DynamicData für alle Gelegenheiten enthält.