En febrero de 2019, se lanzó
ReactiveUI 9 , un marco multiplataforma para crear aplicaciones GUI en la plataforma Microsoft .NET.
ReactiveUI es una herramienta para integrar estrechamente las extensiones reactivas con el patrón de diseño MVVM. El conocimiento del marco puede iniciarse con una
serie de artículos sobre Habré o
desde la portada de la documentación . La actualización ReactiveUI 9 incluye muchas
correcciones y mejoras , pero quizás el cambio más interesante y significativo es la
estrecha integración con el marco DynamicData , que permite trabajar con colecciones cambiantes en un estilo reactivo. ¡Intentemos averiguar en qué casos
DynamicData puede ser
útil y cómo este poderoso marco reactivo está organizado dentro!
Antecedentes
Primero, definimos el rango de tareas que
DynamicData resuelve y descubrimos por qué las herramientas estándar para trabajar con conjuntos de datos cambiantes desde el espacio de nombres
System.Collections.ObjectModel
no nos convienen.
La plantilla MVVM, como saben, implica la división de la responsabilidad entre las capas del modelo, la presentación y el modelo de presentación de la aplicación. La capa del modelo está representada por entidades de dominio y servicios, y no sabe nada sobre el modelo de presentación. La capa del modelo encapsula toda la lógica de la aplicación compleja, y el modelo de presentación delega las operaciones del modelo, dando a la vista acceso a información sobre el estado actual de la aplicación a través de propiedades, comandos y colecciones observables. La herramienta estándar para trabajar con propiedades cambiantes es la interfaz
INotifyPropertyChanged
,
INotifyPropertyChanged
para trabajar con acciones del usuario e
INotifyCollectionChanged
implementar colecciones e implementar
ObservableCollection
y
ReadOnlyObservableCollection
.

La implementación de
INotifyPropertyChanged
e
ICommand
generalmente permanece en la conciencia del desarrollador y el marco MVVM utilizado, ¡pero el uso de
ObservableCollection
impone una serie de restricciones! Por ejemplo, no podemos cambiar una colección de un subproceso en segundo plano sin
Dispatcher.Invoke
o una llamada similar, y esto podría ser útil en caso de trabajar con matrices de datos que alguna operación en segundo plano sincroniza con el servidor. Cabe señalar que en el MVVM idiomático, la capa del modelo no necesita conocer la arquitectura de la aplicación GUI utilizada, y ser compatible con el modelo de MVC o MVP, y es por eso que numerosos
Dispatcher.Invoke
permiten el acceso al control de la interfaz de usuario desde el hilo de fondo en ejecución en un servicio de dominio, viola el principio de compartir la responsabilidad entre las capas de la aplicación.
Por supuesto, en un servicio de dominio sería posible declarar un evento y, como argumento de un evento, pasar un fragmento con los datos modificados. Luego suscríbase al evento, envuelva el
Dispatcher.Invoke
llamada en una interfaz para que no dependa del marco GUI utilizado, mueva
Dispatcher.Invoke
al modelo de presentación y cambie la
ObservableCollection
sea necesario, sin embargo, hay una manera mucho más simple y elegante de resolver el rango de tareas indicado sin tener que escribir una bicicleta . ¡Comencemos el estudio!
Extensiones reactivas. Administrar flujos de datos
Para una comprensión completa de las abstracciones introducidas por
DynamicData y los principios de trabajar con conjuntos de datos reactivos cambiantes, recordemos
qué es la programación reactiva y cómo aplicarla en el contexto de la plataforma Microsoft .NET y el patrón de diseño MVVM . Una forma de organizar la interacción entre los componentes del programa puede ser interactiva y reactiva. En la interacción interactiva, la función del consumidor recibe sincrónicamente datos de la función del proveedor (enfoque basado en extracción,
T
,
IEnumerable
), y en la interacción reactiva, la función del consumidor entrega datos asincrónicamente a la función del consumidor (enfoque basado en inserción,
Task
,
IObservable
).
La programación reactiva es la programación usando flujos de datos asíncronos, y las extensiones reactivas son un caso especial de su implementación, basada en las
IObserver
IObservable
e
IObserver
del espacio de nombres del sistema, que define una serie de operaciones similares a LINQ en la interfaz
IObservable
, llamada LINQ over Observable. Las extensiones reactivas son compatibles con .NET Standard y funcionan donde sea que funcione la plataforma Microsoft .NET.

El marco ReactiveUI invita a los desarrolladores de aplicaciones a aprovechar la implementación reactiva de
ICommand
e
INotifyPropertyChanged
, proporcionando herramientas poderosas como
ReactiveCommand<TIn, TOut>
y
WhenAnyValue
.
WhenAnyValue
permite convertir una propiedad de una clase que implementa INotifyPropertyChanged en una secuencia de eventos de tipo
IObservable<T>
, lo que simplifica la implementación de propiedades dependientes.
public class ExampleViewModel : ReactiveObject { [Reactive]
ReactiveCommand<TIn, TOut>
permite trabajar con el comando, como con un evento de tipo
IObservable<TOut>
, que se publica cada vez que el comando completa la ejecución. Además, cualquier comando tiene una propiedad
ThrownExceptions
de tipo
IObservable<Exception>
.
Todo este tiempo trabajamos con
IObservable<T>
, como con un evento que publica un nuevo valor de tipo
T
cada vez que cambia el estado del objeto que se está monitoreando. En pocas palabras,
IObservable<T>
es una secuencia de eventos, una secuencia que se extiende a lo largo del tiempo.
Por supuesto, podríamos trabajar de manera igual y fácil con colecciones: cada vez que cambie una colección, publique una nueva colección con elementos modificados. En este caso, el valor publicado sería del tipo
IEnumerable<T>
o más especializado, y el evento en sí sería del tipo
IObservable<IEnumerable<T>>
. Pero, como señala correctamente el lector crítico, esto está plagado de serios problemas con el rendimiento de la aplicación, especialmente si no hay una docena de elementos en nuestra colección, ¡sino cien, o incluso varios miles!
Introducción a DynamicData
DynamicData es una biblioteca que le permite utilizar todo el poder de las extensiones reactivas cuando trabaja con colecciones. Las extensiones reactivas listas para usar no proporcionan formas óptimas de trabajar con conjuntos de datos cambiantes, y
el trabajo de DynamicData es solucionarlo. En la mayoría de las aplicaciones, existe la necesidad de actualizar dinámicamente las colecciones; por lo general, una colección se llena con algunos elementos cuando se inicia la aplicación, y luego se actualiza de forma asíncrona, sincronizando la información con un servidor o una base de datos. Las aplicaciones modernas son bastante complejas y, a menudo, es necesario crear proyecciones de colecciones: filtrar, transformar u ordenar elementos. DynamicData fue diseñado solo para deshacerse del código increíblemente complejo que necesitaríamos para administrar conjuntos de datos que cambian dinámicamente. La herramienta se desarrolla y finaliza activamente, y ahora se admiten más de 60 operadores para trabajar con colecciones.
DynamicData no es una implementación alternativa de
ObservableCollection<T>
. La arquitectura
DynamicData se basa principalmente en los conceptos de programación específica del dominio. La ideología de uso se basa en el hecho de que administra una fuente de datos, una colección a la que tiene acceso el código responsable de sincronizar y cambiar los datos. A continuación, aplica varios operadores a la fuente, con los que puede transformar los datos de forma declarativa, sin la necesidad de crear y modificar manualmente otras colecciones. De hecho, con
DynamicData separa las operaciones de lectura y escritura, y solo puede leer de manera reactiva; por lo tanto, las colecciones heredadas siempre se sincronizarán con la fuente.
En lugar del clásico
IObservable<T>
, DynamicData define operaciones en
IObservable<IChangeSet<T>>>
y
IObservable<IChangeSet<TValue, TKey>>
, donde
IChangeSet
es un fragmento que contiene información sobre el cambio de colección: el tipo de cambio y los elementos que se vieron afectados. Este enfoque puede mejorar significativamente el rendimiento del código para trabajar con colecciones escritas en un estilo reactivo. Al mismo tiempo,
IObservable<IChangeSet<T>>
siempre se puede transformar en un
IObservable<IEnumerable<T>>
si es necesario procesar todos los elementos de la colección a la vez. Si suena complicado, ¡no se alarme, a partir de los ejemplos de código todo se volverá claro y transparente!
Ejemplo de DynamicData
Veamos una serie de ejemplos para comprender mejor cómo funciona DynamicData, cómo se diferencia de
System.Reactive
y qué tareas pueden resolver los desarrolladores comunes de software de aplicaciones con una GUI. Comencemos con un ejemplo completo
publicado por DynamicData en GitHub . En el ejemplo, la fuente de datos es
SourceCache<Trade, long>
, que contiene una colección de transacciones. La tarea es mostrar solo transacciones activas, transformar modelos en objetos proxy, ordenar la colección.
En el ejemplo anterior, cuando cambia el
SourceCache
, que es el origen de datos,
ReadOnlyObservableCollection
también cambiará en consecuencia. En este caso, al eliminar elementos de la colección, se llamará al método
Dispose
, la colección siempre se actualizará solo en la secuencia de la GUI y permanecerá ordenada y filtrada. Genial, sin
Dispatcher.Invoke
y código complicado!
Fuentes de datos SourceList y SourceCache
DynamicData proporciona dos colecciones especializadas que pueden usarse como fuente de datos mutable. Estas colecciones son de los tipos
SourceList
y
SourceCache<TObject, TKey>
. Se recomienda usar
SourceCache
siempre que
TObject
tenga una clave única; de lo contrario, use
SourceList
. Estos objetos proporcionan la conocida API de desarrolladores de .NET para modificar datos:
Add
,
Remove
,
Insert
y similares. Para convertir fuentes de datos a
IObservable<IChangeSet<T>>
o
IObservable<IChangeSet<T, TKey>>
, use el operador
.Connect()
. Por ejemplo, si tiene un servicio que actualiza la colección de elementos en segundo plano, puede sincronizar fácilmente la lista de estos elementos con la GUI, sin
Dispatcher.Invoke
y excesos arquitectónicos:
public class BackgroundService : IBackgroundService {
DynamicData utiliza tipos .NET integrados para asignar datos al mundo exterior. Usando los poderosos operadores DynamicData, podemos transformar el
IObservable<IChangeSet<Trade>>
en
ReadOnlyObservableCollection
nuestro modelo de vista.
public class TradesViewModel : ReactiveObject { private readonly ReadOnlyObservableCollection<TradeVm> _trades; public ReadOnlyObservableCollection<TradeVm> Trades => _trades; public TradesViewModel(IBackgroundService background) {
Además de
Transform
,
Filter
y
Sort
, DynamicData incluye una gran cantidad de otros operadores, admite agrupación, operaciones lógicas, suavizar una colección, usar funciones de agregación, excluir elementos idénticos, contar elementos e incluso virtualizar a nivel del modelo de presentación. Lea más sobre todos los operadores en el
proyecto README en GitHub .

Colecciones de un solo hilo y seguimiento de cambios
Además de
SourceList
y
SourceCache
, la biblioteca DynamicData también incluye una implementación de subproceso único de una colección mutable:
ObservableCollectionExtended
. Para sincronizar dos colecciones en su modelo de vista, declare una como
ObservableCollectionExtended
y la otra como
ReadOnlyObservableCollection
y use el operador
ToObservableChangeSet
, que se comporta igual que
Connect
pero está diseñado para funcionar con
ObservableCollection
.
DynamicData también admite el seguimiento de cambios en las clases que implementan la interfaz
INotifyPropertyChanged
. Por ejemplo, si desea que se le notifique un cambio de colección cada vez que cambie una propiedad de un elemento, use la
AutoRefresh
y pase el selector de la propiedad deseada con el argumento.
AutoRefesh
y otros operadores DynamicData le permitirán validar de manera fácil y natural la gran cantidad de formularios y
AutoRefesh
muestran en la pantalla!
Basado en la funcionalidad DynamicData, puede crear rápidamente interfaces bastante complejas, esto es especialmente cierto para los sistemas que muestran una gran cantidad de datos en tiempo real, sistemas de mensajería instantánea y sistemas de monitoreo.

Conclusión
Las extensiones reactivas son una herramienta poderosa que le permite trabajar de manera declarativa con los datos y la interfaz de usuario, escribir código portátil y compatible, y resolver problemas complejos de una manera simple y elegante.
ReactiveUI permite a los desarrolladores de .NET integrar estrechamente las extensiones reactivas en sus proyectos utilizando la arquitectura MVVM al proporcionar implementaciones reactivas de
INotifyPropertyChanged
e
ICommand
, mientras que
DynamicData se encarga de la sincronización de la colección al implementar
INotifyCollectionChanged
, ampliando las capacidades de las extensiones reactivas y cuidando el rendimiento.
Las
bibliotecas ReactiveUI y
DynamicData son compatibles con los marcos de GUI más populares de la plataforma .NET, incluidas Windows Presentation Foundation, Universal Windows Platform,
Avalonia , Xamarin.Android, Xamarin Forms, Xamarin.iOS. Puede comenzar a aprender DynamicData desde la
página de documentación de ReactiveUI correspondiente . También asegúrese de revisar el proyecto
DynamicData Snippets , que contiene ejemplos del uso de DynamicData para todas las ocasiones.