Em fevereiro de 2019, foi lançado o
ReactiveUI 9 , uma estrutura de plataforma cruzada para a criação de aplicativos GUI na plataforma Microsoft .NET.
O ReactiveUI é uma ferramenta para integrar firmemente extensões reativas ao padrão de design do MVVM. O conhecimento da estrutura pode ser iniciado com uma
série de artigos sobre Habré ou
na primeira página da documentação . A atualização do ReactiveUI 9 inclui muitas
correções e melhorias , mas talvez a mudança mais interessante e significativa seja a
forte integração com a estrutura DynamicData , que permite trabalhar com a alteração de coleções em um estilo reativo. Vamos tentar descobrir em que casos o
DynamicData pode ser
útil e como essa poderosa estrutura reativa é organizada por dentro!
Antecedentes
Primeiro, definimos o intervalo de tarefas que o
DynamicData resolve e descobrimos por que as ferramentas padrão para trabalhar com a alteração de conjuntos de dados no namespace
System.Collections.ObjectModel
não nos convém.
O modelo MVVM, como você sabe, envolve a divisão de responsabilidade entre as camadas do modelo, apresentação e modelo de apresentação do aplicativo. A camada de modelo é representada por entidades e serviços de domínio e não sabe nada sobre o modelo de apresentação. A camada do modelo encapsula toda a lógica complexa do aplicativo e o modelo de apresentação delega as operações do modelo, fornecendo à visualização acesso a informações sobre o estado atual do aplicativo por meio de propriedades, comandos e coleções observáveis. A ferramenta padrão para trabalhar com propriedades alteradas é a interface
INotifyPropertyChanged
,
INotifyPropertyChanged
para trabalhar com ações do usuário e
INotifyCollectionChanged
implementar coleções e implementar
ObservableCollection
e
ReadOnlyObservableCollection
.

A implementação de
INotifyPropertyChanged
e
ICommand
geralmente permanece na consciência do desenvolvedor e da estrutura MVVM usada, mas o uso de
ObservableCollection
impõe várias restrições! Por exemplo, não podemos alterar uma coleção de um encadeamento em segundo plano sem
Dispatcher.Invoke
ou uma chamada semelhante, e isso pode ser útil no caso de trabalhar com matrizes de dados que alguma operação em segundo plano sincronize com o servidor. Deve-se notar que, no MVVM idiomático, a camada de modelo não precisa conhecer a arquitetura do aplicativo GUI usado e ser compatível com o modelo do MVC ou MVP, e é por isso que vários
Dispatcher.Invoke
permitem acesso ao controle da interface do usuário a partir do encadeamento em execução em um serviço de domínio, viole o princípio de compartilhar responsabilidades entre as camadas de aplicativos.
Obviamente, em um serviço de domínio, seria possível declarar um evento e, como argumento de um evento, transmitir um pedaço com dados alterados. Em seguida, inscreva-se no evento, envolva a chamada
Dispatcher.Invoke
em uma interface para que ela não dependa da estrutura da GUI usada, mova
Dispatcher.Invoke
para o modelo de apresentação e altere a
ObservableCollection
necessário, no entanto, existe uma maneira muito mais simples e elegante de resolver o intervalo de tarefas indicado sem precisar escrever uma bicicleta . Vamos começar o estudo!
Extensões reativas. Gerenciar fluxos de dados
Para um entendimento completo das abstrações introduzidas pelo
DynamicData e dos princípios de trabalhar com a alteração de conjuntos de dados reativos, lembremos o
que é programação reativa e como aplicá-la no contexto da plataforma Microsoft .NET e do padrão de design MVVM . Uma maneira de organizar a interação entre os componentes do programa pode ser interativa e reativa. Na interação interativa, a função do consumidor recebe dados de forma síncrona da função do provedor (abordagem baseada em pull,
T
,
IEnumerable
) e, na interação reativa, a função do consumidor entrega assincronamente dados para a função do consumidor (abordagem baseada em push,
Task
,
IObservable
).
A programação reativa é a programação usando fluxos de dados assíncronos, e as extensões reativas são um caso especial de sua implementação, com base nas
IObserver
IObservable
e
IObserver
do namespace System, que define várias operações semelhantes a LINQ na interface
IObservable
, denominadas LINQ over Observable. As extensões reativas suportam o .NET Standard e funcionam onde quer que a plataforma Microsoft .NET funcione.

A estrutura ReactiveUI convida os desenvolvedores de aplicativos a aproveitar a implementação reativa de
ICommand
e
INotifyPropertyChanged
, fornecendo ferramentas poderosas como
ReactiveCommand<TIn, TOut>
e
WhenAnyValue
.
WhenAnyValue
permite converter uma propriedade de uma classe que implementa INotifyPropertyChanged em um fluxo de eventos do tipo
IObservable<T>
, que simplifica a implementação de propriedades dependentes.
public class ExampleViewModel : ReactiveObject { [Reactive]
ReactiveCommand<TIn, TOut>
permite que você trabalhe com o comando, como em um evento do tipo
IObservable<TOut>
, publicado sempre que o comando concluir a execução. Além disso, qualquer comando possui uma propriedade
ThrownExceptions
do tipo
IObservable<Exception>
.
Todo esse tempo trabalhamos com
IObservable<T>
, como em um evento que publica um novo valor do tipo
T
sempre que o estado do objeto que está sendo monitorado é alterado. Simplificando,
IObservable<T>
é um fluxo de eventos, uma sequência estendida ao longo do tempo.
Obviamente, poderíamos trabalhar de maneira tão fácil e natural com as coleções - sempre que uma coleção for alterada, publique uma nova coleção com elementos alterados. Nesse caso, o valor publicado seria do tipo
IEnumerable<T>
ou mais especializado, e o próprio evento seria do tipo
IObservable<IEnumerable<T>>
. Mas, como o leitor que pensa criticamente corretamente aponta, isso está repleto de sérios problemas com o desempenho do aplicativo, especialmente se não houver uma dúzia de elementos em nossa coleção, mas uma centena ou até vários milhares!
Introdução ao DynamicData
DynamicData é uma biblioteca que permite que você use toda a potência de extensões reativas ao trabalhar com coleções. As extensões reativas prontas para uso não oferecem maneiras ideais de trabalhar com conjuntos de dados alterados, e
o trabalho da DynamicData é corrigi-lo. Na maioria dos aplicativos, é necessário atualizar dinamicamente as coleções - geralmente, uma coleção é preenchida com alguns elementos quando o aplicativo é iniciado e, em seguida, é atualizada de forma assíncrona, sincronizando informações com um servidor ou banco de dados. As aplicações modernas são bastante complexas e, muitas vezes, é necessário criar projeções de coleções - filtrar, transformar ou classificar elementos. O DynamicData foi projetado apenas para se livrar do código incrivelmente complexo que precisaríamos para gerenciar conjuntos de dados dinamicamente alterados. A ferramenta é ativamente desenvolvida e finalizada, e agora são suportados mais de 60 operadores para trabalhar com coleções.
DynamicData não é uma implementação alternativa de
ObservableCollection<T>
. A arquitetura
DynamicData é baseada principalmente nos conceitos de programação específica de domínio. A ideologia de uso é baseada no fato de você gerenciar algumas fontes de dados, uma coleção à qual o código responsável pela sincronização e alteração de dados tem acesso. Em seguida, você aplica vários operadores à origem, com os quais é possível transformar declarativamente os dados, sem a necessidade de criar e modificar manualmente outras coleções. De fato, com o
DynamicData, você separa as operações de leitura e gravação e só pode ler de maneira reativa - portanto, as coleções herdadas sempre serão sincronizadas com a fonte.
Em vez do
IObservable<T>
clássico
IObservable<T>
, DynamicData define operações em
IObservable<IChangeSet<T>>>
e
IObservable<IChangeSet<TValue, TKey>>
, em que
IChangeSet
é um bloco contendo informações sobre a alteração da coleção - o tipo de alteração e os elementos afetados. Essa abordagem pode melhorar significativamente o desempenho do código para trabalhar com coleções gravadas em um estilo reativo. Ao mesmo tempo,
IObservable<IChangeSet<T>>
sempre pode ser transformado em um
IObservable<IEnumerable<T>>
regular, se for necessário processar todos os elementos da coleção de uma só vez. Se parecer complicado - não se assuste, a partir dos exemplos de código tudo ficará claro e transparente!
Exemplo de DynamicData
Vejamos uma série de exemplos para entender melhor como o DynamicData funciona, como ele difere do
System.Reactive
e quais tarefas os desenvolvedores comuns de software de aplicativo com uma GUI podem resolver. Vamos começar com um exemplo abrangente
publicado por DynamicData no GitHub . No exemplo, a fonte de dados é
SourceCache<Trade, long>
, que contém uma coleção de transações. A tarefa é mostrar apenas transações ativas, transformar modelos em objetos proxy, classificar a coleção.
No exemplo acima, quando você altera o
SourceCache
, que é a fonte de dados, o
ReadOnlyObservableCollection
também muda de acordo. Nesse caso, ao excluir elementos da coleção, o método
Dispose
será chamado, a coleção sempre será atualizada apenas no fluxo da GUI e permanecerá classificada e filtrada. Legal, não
Dispatcher.Invoke
e código complicado!
Fontes de dados SourceList e SourceCache
O DynamicData fornece duas coleções especializadas que podem ser usadas como uma fonte de dados mutável. Essas coleções são dos tipos
SourceList
e
SourceCache<TObject, TKey>
. É recomendável usar o
SourceCache
sempre que o
TObject
tiver uma chave exclusiva; caso contrário, use o
SourceList
. Esses objetos fornecem a familiar API de desenvolvedores .NET para modificar dados -
Add
,
Remove
,
Insert
e similares. Para converter fontes de dados em
IObservable<IChangeSet<T>>
ou
IObservable<IChangeSet<T, TKey>>
, use o operador
.Connect()
. Por exemplo, se você tiver um serviço que atualize a coleção de elementos em segundo plano, poderá sincronizar facilmente a lista desses elementos com a GUI, sem
Dispatcher.Invoke
Invoque excessos arquitetônicos e:
public class BackgroundService : IBackgroundService {
O DynamicData usa tipos .NET internos para mapear dados para o mundo externo. Usando os poderosos operadores DynamicData, podemos transformar o
IObservable<IChangeSet<Trade>>
em
ReadOnlyObservableCollection
nosso modelo de exibição.
public class TradesViewModel : ReactiveObject { private readonly ReadOnlyObservableCollection<TradeVm> _trades; public ReadOnlyObservableCollection<TradeVm> Trades => _trades; public TradesViewModel(IBackgroundService background) {
Além de
Transform
,
Filter
and
Sort
, o DynamicData inclui uma série de outros operadores, suporta operações lógicas de agrupamento, suaviza uma coleção, utiliza funções agregadas, excluindo elementos idênticos, contando elementos e até virtualizando no nível do modelo de representação. Leia mais sobre todos os operadores no
projeto README no GitHub .

Coleções únicas e rastreamento de alterações
Além de
SourceList
e
SourceCache
, a biblioteca DynamicData também inclui uma implementação de thread único de uma coleção mutável -
ObservableCollectionExtended
. Para sincronizar duas coleções no seu modelo de exibição, declare uma como
ObservableCollectionExtended
e a outra como
ReadOnlyObservableCollection
e use o operador
ToObservableChangeSet
, que se comporta da mesma forma que o
Connect
mas foi projetado para funcionar com o
ObservableCollection
.
O DynamicData também oferece suporte ao rastreamento de alterações nas classes que implementam a interface
INotifyPropertyChanged
. Por exemplo, se você deseja ser notificado de uma alteração na coleção sempre que a propriedade de um item for alterada, use a
AutoRefresh
e passe o seletor da propriedade desejada com o argumento
AutoRefesh
e outros operadores DynamicData permitirão que você valide fácil e naturalmente o grande número de formulários e subformulários exibidos na tela!
Com base na funcionalidade DynamicData, você pode criar rapidamente interfaces bastante complexas - isso é especialmente verdadeiro para sistemas que exibem uma grande quantidade de dados em tempo real, sistemas de mensagens instantâneas e sistemas de monitoramento.

Conclusão
As extensões reativas são uma ferramenta poderosa que permite trabalhar declarativamente com dados e a interface do usuário, escrever códigos portáteis e compatíveis e resolver problemas complexos de uma maneira simples e elegante.
O ReactiveUI permite que os desenvolvedores .NET integrem estreitamente extensões reativas em seus projetos usando a arquitetura MVVM, fornecendo implementações reativas de
INotifyPropertyChanged
e
ICommand
, enquanto
DynamicData cuida da sincronização de coleções implementando
INotifyCollectionChanged
, expandindo os recursos de extensões reativas e cuidando do desempenho.
As
bibliotecas ReactiveUI e
DynamicData são compatíveis com as estruturas de GUI mais populares da plataforma .NET, incluindo o Windows Presentation Foundation, Universal Windows Platform,
Avalonia , Xamarin.Android, Xamarin Forms, Xamarin.iOS. Você pode começar a aprender DynamicData na
página de documentação correspondente do ReactiveUI . Verifique também o projeto
DynamicData Snippets , que contém exemplos de uso do DynamicData para todas as ocasiões.