Février 2019 a marqué la sortie de ReactiveUI 9 - le framework multiplateforme pour la construction d'applications GUI sur la plate-forme Microsoft .NET. ReactiveUI est un outil pour une intégration étroite des extensions réactives avec le modèle de conception MVVM. Vous pouvez vous familiariser avec le framework via une série de vidéos ou la page d'accueil de la documentation . La mise à jour ReactiveUI 9 comprend de nombreuses corrections et améliorations , mais la plus cruciale et la plus intéressante est probablement l' intégration avec le cadre DynamicData , vous permettant de travailler avec des collections dynamiques de façon réactive. Voyons à quoi nous pouvons utiliser DynamicData et comment ce puissant framework réactif fonctionne sous le capot!
Présentation
Déterminons d'abord les cas d'utilisation de DynamicData et découvrons ce que nous n'aimons pas des outils par défaut pour travailler avec des jeux de données dynamiques à partir de l'espace de noms System.Collections.ObjectModel
.
Comme nous le savons, le modèle MVVM assume la répartition des responsabilités entre la couche modèle, la couche présentation et le modèle de présentation d'application, également appelé modèle de vue. La couche de modèle est représentée par des entités de domaine et des services et ne sait rien de la couche de modèle de vue. Le modèle encapsule toute la logique complexe de l'application, tandis que le modèle de vue délègue les opérations au modèle, fournissant l'accès à des informations sur l'état actuel de l'application via des propriétés, commandes et collections observables, à la vue. L'outil par défaut pour travailler avec les propriétés dynamiques est l'interface INotifyPropertyChanged
, pour travailler avec les actions utilisateur - ICommand
, et pour travailler avec les collections - l'interface INotifyCollectionChanged
, ainsi que de telles implémentations, comme ObservableCollection<T>
et ReadOnlyObservableCollection<T>
.

Les implémentations des INotifyPropertyChanged
et ICommand
sont généralement à la charge du développeur et du framework MVVM utilisé, mais l'utilisation de la ObservableCollection<T>
par défaut impose un certain nombre de limitations! Par exemple, nous ne pouvons pas muter la collection à partir d'un thread d'arrière-plan sans Dispatcher.Invoke
ou un appel similaire, et cela aurait été très utile pour synchroniser les tableaux de données avec le serveur via une opération en arrière-plan. Il convient de noter que lors de l'utilisation de l'architecture MVVM propre, la couche modèle ne doit rien savoir du cadre GUI utilisé et doit être compatible avec la couche modèle dans la terminologie MVC ou MVP. C'est pourquoi ces nombreux appels Dispatcher.Invoke
dans les services de domaine violent le principe de séparation des responsabilités.
Bien sûr, nous pourrions déclarer un événement dans un service de domaine et transmettre un morceau avec des éléments modifiés comme arguments d'événement, puis souscrire à l'événement, encapsuler l'appel Dispatcher.Invoke
derrière une interface, afin que notre application ne dépende d'aucune interface graphique. , appelez cette interface à partir du modèle de vue et modifiez ObservableCollection<T>
conséquence, mais il existe une manière beaucoup plus élégante de traiter ces problèmes sans avoir à réinventer la roue. Qu'attendons-nous donc?
Extensions réactives. Gestion des flux de données observables
Pour bien comprendre les abstractions introduites par DynamicData et comment travailler avec des ensembles de données réactifs changeants, rappelons ce qu'est la programmation réactive et comment l'utiliser dans le contexte de la plate-forme Microsoft .NET et du modèle de conception MVVM . La manière d'organiser l'interaction entre les composantes du programme peut être interactive ou réactive. Avec l'approche interactive, le consommateur reçoit les données du producteur de manière synchrone (basée sur l'extraction, T, IEnumerable), et avec l'approche réactive, le producteur envoie les données au consommateur de manière asynchrone (basée sur les push, Task, IObservable).

La programmation réactive consiste à programmer avec des flux de données asynchrones, et les extensions réactives sont l'implémentation de programmation réactive, basée sur les interfaces IObservable
et IObserver
de l'espace de noms système, définissant une série d'opérations de type LINQ sur l'interface IObservable
, connue sous le nom de LINQ over Observable. Les extensions réactives prennent en charge .NET Standard et s'exécutent partout où Microsoft .NET s'exécute.

ReactiveUI propose aux développeurs d'applications de tirer parti de l'utilisation des implémentations réactives pour les interfaces ICommand
et INotifyPropertyChanged
, en fournissant des outils tels que ReactiveCommand<TIn, TOut>
et WhenAnyValue
. WhenAnyValue
vous permet de convertir une propriété d'une classe qui implémente l'interface INotifyPropertyChanged
en un flux d'événements de type IObservable<T>
, cela simplifie l'implémentation des propriétés dépendantes.
public class ExampleViewModel : ReactiveObject { [Reactive]
ReactiveCommand<TIn, TOut>
vous permet de travailler avec une commande comme avec IObservable<TOut>
, qui est publiée chaque fois qu'une commande termine son exécution. En outre, toute commande possède une propriété ThrownExceptions
de type IObservable<Exception>
.
Jusqu'à cette époque, nous travaillions avec IObservable<T>
, comme avec un événement qui publie une nouvelle valeur de type T
chaque fois que l'état de l'objet observé change. Autrement dit, IObservable<T>
est un flux d'événements, une collection de type T
étirée dans le temps.
Bien sûr, nous pourrions travailler avec des collections tout aussi facilement et naturellement - chaque fois qu'une collection change, nous pourrions 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 le flux observable lui-même serait de type IObservable<IEnumerable<T>>
. Mais, comme le note à juste titre un lecteur critique, cela pose des problèmes de performances critiques, surtout s'il n'y a pas une douzaine d'éléments dans notre collection, mais une centaine, voire quelques milliers!
Introduction à DynamicData
DynamicData est une bibliothèque qui vous permet d'utiliser la puissance des extensions réactives lorsque vous travaillez avec des collections. Rx est extrêmement puissant, mais prêt à l'emploi ne fournit rien pour aider à la gestion des collections, et DynamicData corrige cela. Dans la plupart des applications, il est nécessaire de mettre à jour dynamiquement les collections - généralement, une collection est remplie d'éléments au démarrage de l'application, puis la collection 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 des éléments. DynamicData a été conçu pour se débarrasser du code incroyablement complexe dont nous aurions besoin pour gérer des ensembles de données à changement dynamique. L'outil se développe et s'affine activement, et maintenant plus de 60 opérateurs sont pris en charge pour travailler avec les collections.

DynamicData n'est pas une implémentation alternative d' ObservableCollection<T>
. L'architecture de DynamicData est basée principalement sur des concepts de programmation pilotés par domaine. L'idéologie d'utilisation est basée sur le fait que vous contrôlez une certaine source de données, une collection à laquelle le code responsable de la synchronisation et de la mutation des données a accès. Ensuite, vous appliquez une série d'opérateurs à la source de données, avec l'aide de ces opérateurs, vous pouvez transformer les données de manière déclarative 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 resteront 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 la collection, y compris le type de changement et les éléments affectés. Cette approche peut améliorer considérablement les performances du code pour travailler avec des collections, écrites de manière réactive. Vous pouvez toujours transformer IObservable<IChangeSet<T>>
en IObservable<IEnumerable<T>>
, s'il devient nécessaire d'accéder à tous les éléments d'une collection à la fois. Si cela semble difficile - ne vous inquiétez pas, les exemples de code ci-dessous clarifieront tout!
Des données dynamiques en action
Examinons un certain nombre d'exemples afin de mieux comprendre comment DynamicData fonctionne, en quoi il diffère de System. Reactive et quelles tâches les développeurs ordinaires de logiciels GUI peuvent aider à résoudre. Commençons par un exemple complet publié sur GitHub . Dans cet exemple, la source de données est SourceCache<Trade, long>
contenant une collection de transactions. L'objectif est de n'afficher que les transactions actives, de transformer les modèles en objets proxy, de trier la collection.
Dans l'exemple ci-dessus, lors de la modification de SourceCache
qui est la source des données, ReadOnlyObservableCollection
change également en conséquence. Dans le même temps, lors de la suppression d'éléments de la collection, la méthode Dispose
sera appelée, la collection sera toujours mise à jour uniquement à partir du thread GUI et restera triée et filtrée. Cool, maintenant nous n'avons plus d'appels Dispatcher.Invoke
et le code est simple et lisible!
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 SourceList<TObject>
et SourceCache<TObject, TKey>
. Il est recommandé d'utiliser SourceCache
chaque fois que TObject
a une clé unique, sinon utilisez SourceList
. Ces objets fournissent le familier pour les développeurs .NET API pour la gestion de collection - des méthodes telles que Add
, Remove
, Insert
. Pour convertir des sources de données en IObservable<IChangeSet<T>>
ou en IObservable<IChangeSet<T, TKey>>
, utilisez l'opérateur .Connect()
. Par exemple, si vous disposez d'un service qui met à jour périodiquement une 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 un code passe-partout similaire:
public class BackgroundService : IBackgroundService {
Avec l'aide des puissants opérateurs DynamicData, nous pouvons transformer IObservable<IChangeSet<Trade>>
en ReadOnlyObservableCollection
déclaré dans 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 des opérateurs de Transform
, de Filter
et de Sort
, DynamicData prend en charge le regroupement, les opérations logiques, l'aplatissement de la collection, l'utilisation de fonctions d'agrégation, l'élimination d'éléments identiques, le comptage d'éléments et même la virtualisation au niveau du modèle de vue. Vous pouvez en savoir plus sur tous les opérateurs dans le fichier README du projet sur GitHub .

Outre SourceList
et SourceCache
, la bibliothèque DynamicData comprend une implémentation de collection mutable à un seul thread - ObservableCollectionExtended
. Pour synchroniser deux collections dans votre modèle de vue, déclarez l'une d'entre elles comme ObservableCollectionExtended
et l'autre comme ReadOnlyObservableCollection
, puis utilisez l'opérateur ToObservableChangeSet
, qui fait presque la même chose que Connect, mais est destiné à 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 recevoir des notifications chaque fois qu'une propriété change, utilisez l'opérateur AutoRefresh
et passez le sélecteur de propriété requis. AutoRefresh
et d'autres opérateurs DynamicData peuvent vous permettre de valider sans effort un grand nombre de formulaires et de formulaires imbriqués affichés à l'écran!
Vous pouvez créer des interfaces utilisateur complexes à l'aide de la fonctionnalité DynamicData, et cela est particulièrement pertinent pour les systèmes affichant une grande quantité de données en temps réel, comme les applications de messagerie instantanée et les systèmes de surveillance.

Conclusion
ReactiveX est un outil puissant vous permettant de travailler avec des flux d'événements et avec l'interface utilisateur, d'écrire du code portable et maintenable et de résoudre des tâches complexes de manière simple et élégante. ReactiveUI permet aux développeurs .NET d'intégrer des extensions réactives dans leurs projets à l'aide de l'architecture MVVM avec des implémentations réactives d' INotifyPropertyChanged
et ICommand
, tandis que DynamicData s'occupe de la gestion de la collection en implémentant INotifyCollectionChanged
, étendant les capacités des extensions réactives en mettant l'accent sur les performances.
Les bibliothèques ReactiveUI et DynamicData sont entièrement compatibles avec tous les frameworks GUI sur la plate-forme .NET, y compris Windows Presentation Foundation, Universal Windows Platform, Avalonia , Xamarin.Android, Xamarin Forms et Xamarin iOS. Vous pouvez commencer à étudier DynamicData sur la page correspondante de la documentation ReactiveUI . Veillez également à vous familiariser avec le projet DynamicData Snippets , qui contient des exemples de code pour presque tout ce dont vous pourriez avoir besoin.