2019年2月,发布了
ReactiveUI 9 ,这是一个用于在Microsoft .NET平台上构建GUI应用程序的跨平台框架。
ReactiveUI是用于将反应式扩展与MVVM设计模式紧密集成的工具。 可以
从 Habré上的
一系列文章或
从文档的首页开始熟悉该框架。 ReactiveUI 9更新包括许多
修复和改进 ,但最有趣和最重要的变化可能是
与DynamicData框架的紧密集成,该框架允许以响应式样式更改集合。 让我们尝试找出在什么情况下
DynamicData可以
派上用场,以及如何在内部安排这种强大的反应式框架!
背景知识
首先,我们定义
DynamicData解决的任务范围,并找出为什么使用
System.Collections.ObjectModel
命名空间中的更改数据集的标准工具不适合我们。
如您所知,MVVM模板涉及模型,表示和应用程序表示模型各层之间的职责划分。 模型层由域实体和服务表示,对表示模型一无所知。 模型层封装了整个复杂的应用程序逻辑,而表示模型则委托了模型的操作,从而通过可观察的属性,命令和集合为视图提供了有关应用程序当前状态信息的视图访问。 用于更改属性的标准工具是
INotifyPropertyChanged
接口,用于处理用户操作的
INotifyPropertyChanged
和
INotifyCollectionChanged
实现集合并实现
ObservableCollection
和
ReadOnlyObservableCollection
INotifyCollectionChanged
。

INotifyPropertyChanged
和
ICommand
的实现通常仍取决于开发人员和所使用的MVVM框架的良知,但是
ObservableCollection
的使用对我们施加了许多限制! 例如,如果没有
Dispatcher.Invoke
或类似的调用,我们将无法从后台线程更改集合,这在处理某些后台操作与服务器同步的数据数组的情况下可能很有用。 应当注意,在惯用的MVVM中,模型层不需要了解所使用的GUI应用程序的体系结构,并且不需要与MVC或MVP中的模型兼容,这就是为什么许多
Dispatcher.Invoke
允许从运行中的后台线程访问用户界面控件的原因在域服务中,违反了在应用程序层之间分担责任的原则。
当然,在域服务中,可以声明一个事件,并作为事件的参数传递带有已更改数据的块。 然后订阅该事件,将
Dispatcher.Invoke
调用包装在接口中,以使其不依赖于所使用的GUI框架,将
Dispatcher.Invoke
移至表示模型并
ObservableCollection
需要更改
ObservableCollection
,但是有一种更简单,更优雅的方法来解决指定范围的任务,而无需编写自行车。 让我们开始学习!
反应性扩展。 管理数据流
为了完全理解
DynamicData引入的抽象以及更改反应式数据集的工作原理,让我们回顾一下
什么是反应式编程以及如何在Microsoft .NET平台和MVVM设计模式的上下文中应用它 。 组织程序组件之间交互的一种方式可以是交互的和反应性的。 在交互式交互中,使用者功能从提供程序功能(基于拉的方法
T
,
IEnumerable
)同步接收数据,在响应式交互中,使用者功能异步将数据传递到使用者功能(基于推方法,
Task
,
IObservable
)。
反应式编程是使用异步数据流进行编程,而反应式扩展是其实现的特例,它基于System名称空间中的
IObservable
和
IObserver
,该
IObserver
在
IObserver
上定义了许多类似于LINQ的操作,称为LINQ over Observable。 反应性扩展支持.NET Standard,并且可以在Microsoft .NET平台工作的任何地方工作。

ReactiveUI框架邀请应用程序开发人员利用
ICommand
和
INotifyPropertyChanged
的响应式实现,提供诸如
ReactiveCommand<TIn, TOut>
和
WhenAnyValue
类的强大工具。
WhenAnyValue
允许
WhenAnyValue
将实现INotifyPropertyChanged的类的属性转换为
IObservable<T>
类型的事件流,从而简化了相关属性的实现。
public class ExampleViewModel : ReactiveObject { [Reactive]
ReactiveCommand<TIn, TOut>
允许您使用该命令,以及
IObservable<TOut>
类型的事件,该事件在命令完成执行时发布。 同样,任何命令都具有类型为
IObservable<Exception>
的
ThrownExceptions
属性。
一直以来,我们一直使用
IObservable<T>
,并且每当被监视对象的状态发生变化时,事件就会发布类型
T
的新值。 简而言之,
IObservable<T>
是事件流,序列随时间延伸。
当然,我们也可以轻松自然地使用集合-每当集合发生更改时,发布具有更改元素的新集合。 在这种情况下,发布的值将为
IEnumerable<T>
类型或更专业,并且事件本身将为
IObservable<IEnumerable<T>>
。 但是,正如具有批判性思维的读者正确指出的那样,这会给应用程序性能带来严重问题,特别是如果我们的收藏中没有十二个元素,而是一百甚至数千个元素!
DynamicData简介
DynamicData是一个库,可让您在使用集合时充分利用反应式扩展的全部功能。 开箱即用的反应式扩展未提供使用最佳方法来更改数据集,而
DynamicData的工作就是修复它。 在大多数应用程序中,需要动态更新集合-通常,在应用程序启动时,集合中会填充一些元素,然后异步更新该集合,从而使信息与服务器或数据库同步。 现代应用程序非常复杂,通常需要创建集合投影-过滤,转换或排序元素。 设计DynamicData只是为了摆脱我们管理动态变化的数据集所需的极其复杂的代码。 该工具正在积极开发和定稿中,目前支持60多个操作员进行馆藏工作。
DynamicData不是
ObservableCollection<T>
的替代实现。
DynamicData体系结构
主要基于特定领域编程的概念。 使用的意识形态基于您管理一些数据源这一事实,该数据源是负责同步和更改数据的代码可以访问的集合。 接下来,您对源应用了多个运算符,您可以使用它们以声明方式转换数据,而无需手动创建和修改其他集合。 实际上,使用
DynamicData可以将读写操作分开,并且只能以反应方式进行读取-因此,继承的集合将始终与源同步。
代替传统的
IObservable<T>
,DynamicData定义了对
IObservable<IChangeSet<T>>>
和
IObservable<IChangeSet<TValue, TKey>>
,其中
IChangeSet
是一个块,其中包含有关集合更改的信息-更改的类型和受影响的元素。 这种方法可以显着提高代码的性能,以处理以反应式编写的集合。 同时,如果有必要立即处理
IObservable<IChangeSet<T>>
则始终可以将其转换为常规
IObservable<IEnumerable<T>>
。 如果听起来很复杂-不要惊慌,从代码示例中,一切将变得清晰透明!
DynamicData示例
让我们看一系列示例,以更好地了解DynamicData的工作原理,它与
System.Reactive
区别以及使用GUI的应用程序软件的普通开发人员可以解决的任务。 让我们从
DynamicData在GitHub上发布的综合示例开始。 在该示例中,数据源是
SourceCache<Trade, long>
,其中包含交易的集合。 任务是仅显示活动事务,将模型转换为代理对象,对集合进行排序。
在上面的示例中,当您更改作为数据源的
SourceCache
时,
ReadOnlyObservableCollection
也将相应地更改。 在这种情况下,从集合中删除元素时,将调用
Dispose
方法,该集合将始终仅在GUI流中进行更新,并保持排序和过滤状态。 很酷,没有
Dispatcher.Invoke
和复杂的代码!
SourceList和SourceCache数据源
DynamicData提供了两个专用集合,可以用作可变数据源。 这些集合的类型为
SourceList
和
SourceCache<TObject, TKey>
。 建议每当
TObject
具有唯一键时都使用
SourceCache
,否则请使用
SourceList
。 这些对象提供了熟悉的.NET开发人员API,用于修改数据-
Add
,
Remove
,
Insert
等。 要将数据源转换为
IObservable<IChangeSet<T>>
或
IObservable<IChangeSet<T, TKey>>
,请使用
IObservable<IChangeSet<T, TKey>>
.Connect()
运算符。 例如,如果您有一项在后台更新元素集合的服务,则可以轻松地将这些元素的列表与GUI同步,而无需
Dispatcher.Invoke
和体系结构方面的麻烦:
public class BackgroundService : IBackgroundService {
DynamicData使用内置的.NET类型将数据映射到外界。 使用强大的DynamicData运算符,我们可以将
IObservable<IChangeSet<Trade>>
转换
ReadOnlyObservableCollection
我们的视图模型的
ReadOnlyObservableCollection
。
public class TradesViewModel : ReactiveObject { private readonly ReadOnlyObservableCollection<TradeVm> _trades; public ReadOnlyObservableCollection<TradeVm> Trades => _trades; public TradesViewModel(IBackgroundService background) {
除了
Transform
,
Filter
和
Sort
,DynamicData还包含许多其他运算符,支持分组,逻辑运算,使用集合函数对集合进行平滑处理,不包括相同的元素,计数元素,甚至在表示模型级别也没有虚拟化。
在GitHub上的
README项目中了解有关所有运算符的更多信息。

单线程集合和变更跟踪
除了
SourceList
和
SourceCache
,DynamicData库还包括可变集合
ObservableCollectionExtended
的单线程实现。 要同步视图模型中的两个集合,请将一个声明为
ObservableCollectionExtended
,将另一个声明为
ReadOnlyObservableCollection
然后使用
ToObservableChangeSet
运算符,其行为与
Connect
相同,但旨在与
ObservableCollection
。
DynamicData还支持跟踪实现
INotifyPropertyChanged
接口的类中的更改。 例如,如果希望每当项目的属性发生更改时就通知集合更改,请使用
AutoRefresh
然后将所需属性的选择器与参数一起传递。
AutoRefesh
和其他DynamicData运算符将使您轻松自然地验证屏幕上显示的大量表单和子表单!
基于DynamicData功能,您可以快速创建相当复杂的界面-对于显示大量实时数据的系统,即时消息传递系统和监视系统尤其如此。

结论
反应性扩展是一个功能强大的工具,可让您声明性地使用数据和用户界面,编写可移植和受支持的代码,并以简单优雅的方式解决复杂的问题。
ReactiveUI允许.NET开发人员通过提供
INotifyPropertyChanged
和
ICommand
响应式实现,使用MVVM体系结构将响应式扩展紧密集成到他们的项目中,而
DynamicData通过实现
INotifyCollectionChanged
来处理集合同步,扩展
INotifyCollectionChanged
扩展的功能并关注性能。
ReactiveUI和
DynamicData库与.NET平台的大多数流行的GUI框架兼容,包括Windows Presentation Foundation,通用Windows平台,Avalonia,Xamarin.Android,Xamarin Forms,Xamarin.iOS。 您可以从
相应的ReactiveUI文档页面开始学习DynamicData。 另外,请确保签出
DynamicData Snippets项目,其中包含在所有场合下使用DynamicData的示例。