En février 2019,
ReactiveUI 9 , un framework multiplateforme pour la construction d'applications GUI sur la plate-forme Microsoft .NET, a été publié.
ReactiveUI est un outil pour intégrer étroitement les extensions réactives avec le modèle de conception MVVM. La connaissance du cadre peut être commencée par une
série d'articles sur Habré ou à
partir de la première page de la documentation . La mise à jour ReactiveUI 9 comprend de nombreuses
corrections et améliorations , mais le changement le plus intéressant et le plus important est peut-être l'
intégration étroite avec le cadre DynamicData , qui permet de travailler avec des collections changeantes dans un style réactif. Essayons de déterminer dans quels cas
DynamicData peut être
utile et comment ce puissant cadre réactif est organisé à l'intérieur!
Contexte
Tout d'abord, nous définissons la gamme de tâches que
DynamicData résout et découvrons pourquoi les outils standard pour travailler avec des ensembles de données changeants de l'espace de noms
System.Collections.ObjectModel
ne nous conviennent pas.
Le modèle MVVM, comme vous le savez, implique la répartition des responsabilités entre les couches du modèle, la présentation et le modèle de présentation de l'application. La couche modèle est représentée par des entités de domaine et des services, et ne sait rien du modèle de présentation. La couche de modèle encapsule l'intégralité de la logique d'application complexe et le modèle de présentation délègue les opérations du modèle, donnant à la vue un accès aux informations sur l'état actuel de l'application via des propriétés, des commandes et des collections observables. L'outil standard pour travailler avec des propriétés changeantes est l'interface
INotifyPropertyChanged
,
INotifyPropertyChanged
pour travailler avec les actions utilisateur et
INotifyCollectionChanged
implémenter des collections et implémenter
ObservableCollection
et
ReadOnlyObservableCollection
.

L'implémentation de
INotifyPropertyChanged
et
ICommand
reste généralement dans la conscience du développeur et du framework MVVM utilisé, mais l'utilisation de
ObservableCollection
impose un certain nombre de restrictions! Par exemple, nous ne pouvons pas modifier une collection à partir d'un thread d'arrière-plan sans
Dispatcher.Invoke
ou un appel similaire, et cela pourrait être utile en cas de travail avec des tableaux de données qu'une opération en arrière-plan synchronise avec le serveur. Il convient de noter que dans la MVVM idiomatique, la couche modèle n'a pas besoin de connaître l'architecture de l'application graphique utilisée et d'être compatible avec le modèle de MVC ou MVP, et c'est pourquoi de nombreux
Dispatcher.Invoke
permettent d'accéder au contrôle de l'interface utilisateur à partir du thread d'arrière-plan en cours d'exécution dans un service de domaine, violent le principe de partage des responsabilités entre les couches d'application.
Bien sûr, dans un service de domaine, il serait possible de déclarer un événement et, comme argument d'un événement, de passer un morceau avec des données modifiées. Ensuite, abonnez-vous à l'événement, encapsulez l'appel
Dispatcher.Invoke
dans une interface afin qu'il ne dépende pas du cadre graphique utilisé, déplacez
Dispatcher.Invoke
vers le modèle de présentation et modifiez
ObservableCollection
besoins, mais il existe un moyen beaucoup plus simple et plus élégant de résoudre la plage de tâches indiquée sans avoir à écrire un vélo . Commençons l'étude!
Extensions réactives. Gérer les flux de données
Pour une compréhension complète des abstractions introduites par
DynamicData et des principes de travail avec des jeux de données réactifs changeants, rappelons
ce qu'est la programmation réactive et comment l'appliquer dans le contexte de la plate-forme Microsoft .NET et du modèle de conception MVVM . Un moyen d'organiser l'interaction entre les composants du programme peut être interactif et réactif. Dans l'interaction interactive, la fonction consommateur reçoit de manière synchrone des données de la fonction fournisseur (approche basée sur l'extraction,
T
,
IEnumerable
), et dans l'interaction réactive, la fonction consommateur fournit de manière asynchrone des données à la fonction consommateur (approche basée sur les push,
Task
,
IObservable
).
La programmation réactive consiste à programmer à l'aide de flux de données asynchrones, et les extensions réactives sont un cas particulier de son implémentation, basé sur les
IObserver
IObservable
et
IObserver
de l'espace de noms System, qui définit un certain nombre d'opérations de type LINQ sur l'interface
IObservable
, appelé LINQ over Observable. Les extensions réactives prennent en charge la norme .NET et fonctionnent partout où la plateforme Microsoft .NET fonctionne.

Le framework ReactiveUI invite les développeurs d'applications à tirer parti de l'implémentation réactive de
ICommand
et
INotifyPropertyChanged
, fournissant des outils puissants tels que
ReactiveCommand<TIn, TOut>
et
WhenAnyValue
.
WhenAnyValue
permet de convertir une propriété d'une classe qui implémente INotifyPropertyChanged en un flux d'événements de type
IObservable<T>
, ce qui simplifie l'implémentation des propriétés dépendantes.
public class ExampleViewModel : ReactiveObject { [Reactive]
ReactiveCommand<TIn, TOut>
vous permet de travailler avec la commande, comme avec un événement de type
IObservable<TOut>
, qui est publié chaque fois que la commande termine son exécution. En outre, toute commande possède une propriété
ThrownExceptions
de type
IObservable<Exception>
.
IObservable<T>
tout ce temps, nous avons travaillé avec
IObservable<T>
, comme avec un événement qui publie une nouvelle valeur de type
T
chaque fois que l'état de l'objet surveillé change. Autrement dit,
IObservable<T>
est un flux d'événements, une séquence étirée dans le temps.
Bien sûr, nous pourrions tout aussi facilement et naturellement travailler avec des collections - chaque fois qu'une collection change, publier une nouvelle collection avec des éléments modifiés. Dans ce cas, la valeur publiée serait de type
IEnumerable<T>
ou plus spécialisée, et l'événement lui-même serait de type
IObservable<IEnumerable<T>>
. Mais, comme le fait remarquer à juste titre le lecteur critique, cela pose de sérieux problèmes de performances applicatives, surtout s'il n'y a pas une douzaine d'éléments dans notre collection, mais une centaine, voire plusieurs milliers!
Introduction à DynamicData
DynamicData est une bibliothèque qui vous permet d'utiliser toute la puissance des extensions réactives lorsque vous travaillez avec des collections. Les extensions réactives
prêtes à l'emploi ne fournissent pas de moyens optimaux pour travailler avec des ensembles de données changeants, et
le travail de DynamicData consiste à le corriger. Dans la plupart des applications, il est nécessaire de mettre à jour dynamiquement les collections - généralement, une collection est remplie de certains éléments au démarrage de l'application, puis elle est mise à jour de manière asynchrone, synchronisant les informations avec un serveur ou une base de données. Les applications modernes sont assez complexes et il est souvent nécessaire de créer des projections de collections - filtrer, transformer ou trier les éléments. DynamicData a été conçu juste pour se débarrasser du code incroyablement complexe dont nous aurions besoin pour gérer des ensembles de données à changement dynamique. L'outil est activement développé et finalisé, et maintenant plus de 60 opérateurs pour travailler avec les collections sont pris en charge.
DynamicData n'est pas une implémentation alternative d'
ObservableCollection<T>
. L'architecture
DynamicData est basée principalement sur les concepts de la programmation spécifique au domaine. L'idéologie d'utilisation est basée sur le fait que vous gérez une source de données, une collection à laquelle le code responsable de la synchronisation et de la modification des données a accès. Ensuite, vous appliquez un certain nombre d'opérateurs à la source, avec lesquels vous pouvez transformer de manière déclarative les données, sans avoir besoin de créer et de modifier manuellement d'autres collections. En fait, avec
DynamicData vous séparez les opérations de lecture et d'écriture, et vous ne pouvez lire que de manière réactive - par conséquent, les collections héritées seront toujours synchronisées avec la source.
Au lieu du classique
IObservable<T>
, DynamicData définit les opérations sur
IObservable<IChangeSet<T>>>
et
IObservable<IChangeSet<TValue, TKey>>
, où
IChangeSet
est un bloc contenant des informations sur le changement de collection - le type de changement et les éléments qui ont été affectés. Cette approche peut considérablement améliorer les performances du code pour travailler avec des collections écrites dans un style réactif. Dans le même temps,
IObservable<IChangeSet<T>>
peut toujours être transformé en un
IObservable<IEnumerable<T>>
s'il devient nécessaire de traiter tous les éléments de la collection à la fois. Si cela semble compliqué - ne vous inquiétez pas, à partir des exemples de code, tout deviendra clair et transparent!
Exemple DynamicData
Examinons une série d'exemples pour mieux comprendre comment DynamicData fonctionne, en quoi il diffère de
System.Reactive
et quelles tâches les développeurs ordinaires de logiciels d'application avec une interface graphique peuvent résoudre. Commençons par un exemple complet
publié par DynamicData sur GitHub . Dans l'exemple, la source de données est
SourceCache<Trade, long>
, qui contient une collection de transactions. La tâche consiste à n'afficher que les transactions actives, à transformer les modèles en objets proxy, à trier la collection.
Dans l'exemple ci-dessus, lorsque vous modifiez le
SourceCache
, qui est la source de données,
ReadOnlyObservableCollection
également en conséquence. Dans ce cas, lors de la suppression d'éléments de la collection, la méthode
Dispose
sera appelée, la collection sera toujours mise à jour uniquement dans le flux GUI et restera triée et filtrée. Cool, pas de
Dispatcher.Invoke
et code compliqué!
Sources de données SourceList et SourceCache
DynamicData fournit deux collections spécialisées qui peuvent être utilisées comme source de données mutable. Ces collections sont de types
SourceList
et
SourceCache<TObject, TKey>
. Il est recommandé d'utiliser
SourceCache
chaque fois que
TObject
a une clé unique, sinon utilisez
SourceList
. Ces objets fournissent l'API de développeurs .NET familière pour la modification des données -
Add
,
Remove
,
Insert
etc. Pour convertir des sources de données en
IObservable<IChangeSet<T>>
ou
IObservable<IChangeSet<T, TKey>>
, utilisez l'opérateur
.Connect()
. Par exemple, si vous disposez d'un service qui met à jour la collection d'éléments en arrière-plan, vous pouvez facilement synchroniser la liste de ces éléments avec l'interface graphique, sans
Dispatcher.Invoke
et les excès architecturaux:
public class BackgroundService : IBackgroundService {
DynamicData utilise des types .NET intégrés pour mapper les données au monde extérieur. En utilisant les puissants opérateurs DynamicData, nous pouvons transformer l'
IObservable<IChangeSet<Trade>>
en
ReadOnlyObservableCollection
notre modèle de vue.
public class TradesViewModel : ReactiveObject { private readonly ReadOnlyObservableCollection<TradeVm> _trades; public ReadOnlyObservableCollection<TradeVm> Trades => _trades; public TradesViewModel(IBackgroundService background) {
En plus de
Transform
,
Filter
et
Sort
, DynamicData comprend une foule d'autres opérateurs, prend en charge le regroupement, les opérations logiques, le lissage d'une collection, l'utilisation de fonctions d'agrégation, à l'exclusion des éléments identiques, des éléments de comptage et même la virtualisation au niveau du modèle de représentation. En savoir plus sur tous les opérateurs du
projet README sur GitHub .

Collections monothread et suivi des modifications
En plus de
SourceList
et
SourceCache
, la bibliothèque DynamicData comprend également une implémentation à un seul thread d'une collection mutable -
ObservableCollectionExtended
. Pour synchroniser deux collections dans votre modèle de vue, déclarez l'une en tant que
ObservableCollectionExtended
et l'autre en tant que
ReadOnlyObservableCollection
et utilisez l'opérateur
ToObservableChangeSet
, qui se comporte de la même manière que
Connect
mais est conçu pour fonctionner avec
ObservableCollection
.
DynamicData prend également en charge le suivi des modifications dans les classes qui implémentent l'interface
INotifyPropertyChanged
. Par exemple, si vous souhaitez être averti d'une modification de collection chaque fois qu'une propriété d'un élément change, utilisez l'
AutoRefresh
et passez le sélecteur de la propriété souhaitée avec l'argument.
AutoRefesh
et d'autres opérateurs DynamicData vous permettront de valider facilement et naturellement le grand nombre de formulaires et sous-formulaires affichés à l'écran!
Grâce à la fonctionnalité DynamicData, vous pouvez créer rapidement des interfaces assez complexes - cela est particulièrement vrai pour les systèmes qui affichent une grande quantité de données en temps réel, les systèmes de messagerie instantanée et les systèmes de surveillance.

Conclusion
Les extensions réactives sont un outil puissant qui vous permet de travailler de manière déclarative avec les données et l'interface utilisateur, d'écrire du code portable et pris en charge et de résoudre des problèmes complexes de manière simple et élégante.
ReactiveUI permet aux développeurs .NET d'intégrer étroitement les extensions réactives dans leurs projets à l'aide de l'architecture MVVM en fournissant des implémentations réactives de
INotifyPropertyChanged
et
ICommand
, tandis que
DynamicData s'occupe de la synchronisation de la collection en implémentant
INotifyCollectionChanged
, étendant les capacités des extensions réactives et en prenant soin des performances.
Les
bibliothèques ReactiveUI et
DynamicData sont compatibles avec les cadres d'interfaces graphiques les plus populaires de la plate-forme .NET, notamment Windows Presentation Foundation, Universal Windows Platform,
Avalonia , Xamarin.Android, Xamarin Forms, Xamarin.iOS. Vous pouvez commencer à apprendre DynamicData à partir de la
page de documentation ReactiveUI correspondante . Assurez-vous également de consulter le projet
DynamicData Snippets , qui contient des exemples d'utilisation de DynamicData pour toutes les occasions.