实验3-引入 .net 中的 events 到 Rx
目标:前面实验中的使用各种工厂构造方法创建一个 可观察序列是一个部分。把 .net 中现有的异步数据源进行关联
是更重要的事情。在这次实验中我们将看到使用 FromEvent 操作来把 .net 中的 event 作为一个 observable 序
列导入到 Rx 。每次事件触发,一个 OnNext 消息会被传递到 observable 序列。
背景:Rx 的目标不是代替现有的异步编程模型,比如 .net 事件,异步模型或者 Task Parallel Library。这些已有
的理论通常很适合直接使用,比如在 C# 中使用 event handles。然而,使用这些低级的理论可能使程序员必须疲
惫地进行资源维护(比如事件的注销 -=)和进行重新改造(比如你知道怎样过滤一个事件吗?)。这样会使程序员
从实际问题的解决上分心。在下面的实验中,我们会展示 Rx 的强大源于何处:异步数据源的组合。
1、Windows Form 是 framework api 中包含很多事件的很好的示例。为了减少干扰,下面不会直接创建一个
Windows Form 工程而是使用一个控制台程序创建。同时需要添加 System.Windows.Forms 和
System.Drawing,也需要引入 System.Reactive 和 System.CoreEx:
2、输入下面的代码来创建一个新 form,并且通过 Application.Run 运行:
3、接下来我们会使用鼠标的 MouseMove 事件:
虽然能很好的工作,但是传统的 .net 事件有很多的限制:
1)事件隐藏了数据源。必须通过事件中的代码看到它。你以前有没有把 MouseMove 事件作为一个 Point
值的一个集合?在 Rx 的世界里,我们把 events 仅仅看作一个固有形式的 observable 序列:你的鼠标是
一个 Point 值的数据库。
2)事件不能传递出去,比如 一个产生 Point 值的事件不能传递出数据作为 GPS 的服务。深层的原因是因为
事件不是 first-class objects。在 Rx 的世界里,每一个 observable 序列可以用一个可以被传递和保持的
对象代表。
3)事件不能被容易的组合。例如,你不能雇佣一个数学家去按照一定的规则写一个可以过滤事件产生的数据
的过滤器。在 Rx 的世界里,由于 observables 的 first-class 的本质,我们可以提供如 Where 这种基本操作
4)Events 需要你手动维护,需要你记得传递给它的委托。在 Rx 的世界里,你可以得到一个 IDisposable 句柄
来 unsubscribe。
4、下面让我们看一下 Rx 的使用。我们使用 FromEvent 方法来向 Rx 导入一个事件,然后可以获得关联事件所获
得的 EventArgs对象。
我们深入的讲解一下:
1)这个 FromEventPattern 方法把指定事件转换成一个传递 IEvent 类型元素(既包含 sender 也
包含 event 参数 )的 observable 序列。把鼠标悬停在 var 关键字上,显示变量 moves 的类型:
2)当调用 Subscribe 时,一个 handler 附加到底层的事件。每次事件触发的时候,它的 sender 和
arguments 被装到一个 IEvent<MouseEventArgs> 对象中,然后被发送到它的所有的观察者
3)在 OnNext handler 里面,因为强类型,我们可以“点” 出事件的 Location 属性。
4)可以使用 FromEvent 操作返回的 IDisposable 对象进行事件句柄的清除。在 using 代码块里会自动
调用 Dispose 方法,自动清除事件句柄。
5、为了讲述更深入一些,我们看一下另一个 Windows Form 事件。首先添加一个 TextBox 控件
到这个 form 对象中:
6、重构代码,来拥有 Form 对象的 MouseMove 和 TextBox 的 TextChanged 事件。这次,我们输出
到控制台作为 log。在实验5 中我们会学到一个叫做 Do 的方法来做这件事。
从上面的代码中我们并没有获得更多。传统的 .net 事件并没有明确的展示 数据导向的本质。这个特别
的事件是一个很好的 observation 的示例:从 TextChanged 事件句柄中,当文字改变时你并没有立即
获得文字,这也是 99% 的 event 的这样做的。
最后,我们注意到 IDisposable 注册句柄的联合类型 System.Disposables 命名空间下的
CompositeDisposable 类。当它调用 dispose 方法时会释放掉 所有它包含的 IDisposable 对象。
7、下面是一段示例的输出:
结论:.NET 中的事件仅仅是一种形式的 异步数据源。为了作为 observable collections 来使用它们,Rx
提供了 FromEvent 方法。返回一个包含 Sender 和 event arguments 的 IEvent 类型的对象。
练习4 - 第一次浏览一些标准的 Query Operators
目标:把 observable 序列作为异步数据源,从而使它们可以像很多其它的数据源一样被查询。
一谈到 C# 编程中的查询,会立即想到 LINQ。在这个实验中,将演示如何使用 LINQ 语句对 observable
序列进行查询。
1、接着上面实验的代码,我们看一下刚才写的使用 FromEvent 操作引入到 Rx 的处理 UI 事件的代码:
回忆一下 moves 和 input 集合的类型都是 IObservable<IEvent<TEventArgs>> 类型的对象,TEventArgs
是从 FromEvent 中获得的参数类型。一般我们不会对捕获的 IEvent 里的参数都感兴趣,我们可以去掉多余的东西。
2、在传统的 .net event 世界里,很多人都会这样写代码:
在第一个示例中使用 if 语句进行过滤;在第二个示例中使用另一个本地变量
3、在 Rx 的这个新世界里,我们可以做得更好。因为 observable 序列可以用 IObservable<T>对象代表
它们所有我们可以为 operators 提供一整套的(扩展)方法。我们使用 LINQ 查询方法重写一下上面的事件
处理代码:
使用 C# 中的查询语句,我们去掉了 IEvent<MouseArgs> 和 IEvent<EventArgs> 数据类型,得到
更喜欢的 Point 和 string。从而我们得到我们想要的 moves 和 input 的更有意义的数据类型的 observable 序列
上面的查询语句可以简化为 query 扩展方法:
背景:把异步数据源定义为 first-class 对象的能力决定了 operators 的定义。
4、为了减少 input 序列中的噪音,我们可以轻松的过滤到鼠标移动的二分线(x 和 y坐标相等的点)。我们可以
使用 LINQ 中的 Where 关键字:
变量 moves 和 overFirstBisector 的类型都是 IObservable<Point>.
5、示例的输出如下,每个传递的鼠标 move 通知 都符合我们指定的过滤查询约束。
结论:我们可以使用连续的操作来过滤出符合我们要求的序列。LINQ 语句提供了一个简单的方式来执行
通常的操作。后面我们会继续讨论。
练习 5 - 更多的查询操作来 驯化 input
目标:observable 序列在正常使用中的行为不是很好。通常我们想获得一种形式的数据,却得到另一种数据形态。
上面的实验已经可以看出一些了。但是通常会有更多形式的病态数据源。例如,会输出重复的值。如果对于用户来说
一个源运行的太快而不好处理时?我们会在下面处理这种情形。
下面演示异步“建议列表”。一个用户在文本框中输入一个词汇,则向用户提供一个以这个词汇为开始的从 web
service 获取的“建议列表”。。为了防止 UI 卡死,我们使用异步的方式。Rx 非常适合处理这种问题。首先
看一下 TextBox 控件的行为。
1、在下面我们不再需要 MouseMove 事件:
2、
稍后继续整理翻译
微软 MSDN Rx 网址:The Reactive Extensions (Rx)
本文翻译原英文文档下载:Hands-on Lab Reactive Extensions for .NET