2019年2月标志着ReactiveUI 9的发布-ReactiveUI 9是用于在Microsoft .NET平台上构建GUI应用程序的跨平台框架。 ReactiveUI是用于将反应式扩展与MVVM设计模式紧密集成的工具。 您可以通过一系列视频或文档的欢迎页面熟悉框架。 ReactiveUI 9更新包括许多修复和改进 ,但最关键和最有趣的更新可能是与DynamicData框架集成 ,使您能够以Reactive方式处理动态集合。 让我们找出可用于DynamicData的功能以及这个强大的反应式框架如何在后台运行!
引言
首先,让我们确定DynamicData的用例,并找出我们对使用System.Collections.ObjectModel
命名空间中的动态数据集的默认工具System.Collections.ObjectModel
。
众所周知,MVVM模板承担了模型层,表示层和应用程序表示模型(也称为视图模型)之间的责任划分。 模型层由域实体和服务表示,对视图模型层一无所知。 该模型封装了应用程序的整个复杂逻辑,而视图模型则将操作委派给该模型,从而通过可观察的属性,命令和集合向视图提供对应用程序当前状态的信息的访问。 用于处理动态属性的默认工具是INotifyPropertyChanged
接口,用于处理用户操作INotifyCollectionChanged
,用于处理集合INotifyCollectionChanged
接口,以及诸如ObservableCollection<T>
和ReadOnlyObservableCollection<T>
。

INotifyPropertyChanged
和ICommand
接口的实现通常取决于开发人员和所使用的MVVM框架,但是使用默认的ObservableCollection<T>
有很多限制! 例如,没有Dispatcher.Invoke
或类似的调用,我们无法从后台线程中更改集合,这对于通过后台操作与服务器同步数据数组非常有用。 值得注意的是,当使用干净的MVVM体系结构时,模型层对所使用的GUI框架一无所知,并且应与MVC或MVP术语中的模型层兼容。 这就是域服务中众多Dispatcher.Invoke
调用违反职责分离原则的原因。
当然,我们可以在域服务中声明一个事件,并传输带有更改项的块作为事件参数,然后订阅该事件,将Dispatcher.Invoke
调用封装在接口后面,因此我们的应用程序将不依赖于任何GUI框架,从视图模型调用该接口并相应地修改ObservableCollection<T>
,但是有很多更优雅的方式来处理此类问题,而无需重新发明。 那我们还在等什么呢?
反应性扩展。 管理可观察的数据流
为了完全理解DynamicData引入的抽象以及如何使用变化的反应式数据集,让我们回顾一下什么是反应式编程以及如何在Microsoft .NET平台和MVVM设计模式的上下文中使用它 。 组织程序组件之间的交互的方式可以是交互的或反应性的。 使用交互式方法,消费者可以同步地从生产者接收数据(基于拉,T,IEnumerable),而采用反应性方法,生产者可以异步将数据推送到消费者(基于推,Task,IObservable)。

反应式编程是使用异步数据流进行编程,反应式扩展是基于系统名称空间中的IObservable
和IObserver
接口的反应式编程实现,在IObserver
接口上定义了一系列类似于LINQ的操作,称为LINQ over Observable。 反应性扩展支持.NET Standard,并且可以在Microsoft .NET运行的任何位置运行。

ReactiveUI通过提供诸如ReactiveCommand<TIn, TOut>
和WhenAnyValue
类的工具,为应用程序开发人员提供了利用ICommand
和INotifyPropertyChanged
接口的反应式实现的优势。 WhenAnyValue
允许您将实现INotifyPropertyChanged
接口的类的属性转换为IObservable<T>
类型的事件流时,这将简化从属属性的实现。
public class ExampleViewModel : ReactiveObject { [Reactive]
ReactiveCommand<TIn, TOut>
允许您使用与IObservable<TOut>
一样的命令,每当命令完成执行时都会发布该命令。 同样,任何命令都具有类型为IObservable<Exception>
的ThrownExceptions
属性。
在此之前,我们一直在使用IObservable<T>
,以及一个事件,只要被观察对象的状态发生变化,该事件就会发布类型T
的新值。 简而言之, IObservable<T>
是事件流,是时间拉伸的T
类型集合。
当然,我们可以轻松自然地使用集合-每当集合发生更改时,我们都可以发布元素已更改的新集合。 在这种情况下,发布的值将是IEnumerable<T>
类型或更专业,并且可观察的流本身将是IObservable<IEnumerable<T>>
。 但是,正如一个思维敏锐的读者正确指出的那样,这充满了关键的性能问题,尤其是如果我们的收藏中没有十二个元素,而是一百甚至数千个元素!
DynamicData简介
DynamicData是一个库,可让您在处理集合时使用反应式扩展的功能。 Rx非常强大,但是开箱即用并没有提供任何帮助来管理集合,而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的工作原理,它与System.Reactive的区别以及GUI软件的普通开发人员可以解决的任务,我们来看一些示例。 让我们从发布在GitHub上的综合示例开始。 在此示例中,数据源是SourceCache<Trade, long>
其中包含交易的集合。 目的是仅显示活动事务,将模型转换为代理对象,对集合进行排序。
在上面的示例中,当更改作为数据源的SourceCache
时, ReadOnlyObservableCollection
也相应地更改。 同时,从集合中删除项目时,将调用Dispose
方法,该集合将始终仅从GUI线程进行更新,并保持排序和过滤状态。 太好了,现在我们没有Dispatcher.Invoke
调用了,代码简单易读!
数据源。 SourceList和SourceCache
DynamicData提供了两个专用集合,可以用作可变数据源。 这些集合是SourceList<TObject>
和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运算符,我们可以将IObservable<IChangeSet<Trade>>
转换为在视图模型中声明的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
运算符并传入所需的属性选择器。 AutoRefresh
和其他DynamicData运算符可以让您毫不费力地验证屏幕上显示的大量表单和嵌套表单!
您可以使用DynamicData功能创建复杂的UI,它尤其适用于实时显示大量数据的系统,例如即时消息传递应用程序和监视系统。

结论
ReactiveX是一个功能强大的工具,可让您使用事件流和UI进行操作,编写可移植且可维护的代码,并以简单而优雅的方式解决复杂的任务。 ReactiveUI允许.NET开发人员使用MVVM体系结构通过INotifyPropertyChanged
和ICommand
响应式实现将响应式扩展集成到他们的项目中,而DynamicData通过实现INotifyCollectionChanged
管理集合管理,以性能为重点扩展INotifyCollectionChanged
扩展的功能。
ReactiveUI和DynamicData库与.NET平台上的所有GUI框架完全兼容,包括Windows Presentation Foundation,通用Windows平台,Avalonia,Xamarin.Android,Xamarin Forms和Xamarin iOS。 您可以在ReactiveUI文档的相应页面上开始研究DynamicData。 还请注意使自己熟悉DynamicData Snippets项目,其中包含几乎所有可能需要的代码示例。