Reactive Extensions (Rx) 入门(4) —— Rx的事件编程②
转自:http://blog.csdn.net/fangxinggood/article/details/7685393
原文:http://www.atmarkit.co.jp/fdotnet/introrx/introrx_01/introrx_02_02.html
作者:河合 宜文
事件是什么?用Rx来处理事件的优势
让我们来看看如何用Rx特有的事件Obserable转换事件处理的。在这之前,让我们考虑一下事件在.NET中的应用场景。
最具有代表性的应该是GUI的事件了。比如:按钮点击,鼠标移动等,这些全是通过事件来处理的。而WindowsPhone(简记为WP7)中增加的比如触摸等手势输入都是事件处理,TabletPC和WP7一样,也配备了传感器,还有最近出尽风头的Kinect设备,也配置了传感器。
另外,还有以通知(推送)为目的,例如INotifyPropertyChanged接口就是利用事件来通知属性发生变化了。还有FileSystemWatcher类是对文件或者文件夹进行变化监视通过事件进行通知。其他还有Timer的事件,WebClient的异步方法等也是利用了事件来处理。
GUI事件:合成
那么用Rx来操作事件有什么爽的呢?如果要说的话,首先是可以“合成”。比如:GUI事件中的鼠标按下/移动/放开 想要对这组操作进行组合的场景,一般的作法都是外部做一个Flag来管理,这样可能会使得代码变得复杂且可读性下降,而且除了这种组合之外,再想在按下/移动/放开事件中处理一些别的,则非常容易使原有代码结构发生破坏。
但如果使用Rx来操作那么则无需使用外部的Flag,直接可以将按下/移动/放开合成为一个新的事件。这样的话,即不用Flag来控制也可以在按下/移动/放开里各自处理事件。代码也不会混在一起,更加简洁。另外,合成的事件可读性也被大大提高了。
下面的代码示例如何将 按下/移动/放开 合成一个新的事件并进行处理:
- // WindowsForm的Drag事件:鼠标左键按下/移动/直到放开过程中
- // 取得鼠标的坐标
- var drag = from down in this.MouseDownAsObservable()
- from move in this.MouseMoveAsObservable().TakeUntil(
- this.MouseUpAsObservable())
- select move.Location;
另外,对于简单的事件处理,可以只利用IObservable<T>对象的OnNext方法。用Rx来处理事件,事件完成(OnCompleted方法),异常处理(OnError方法)是完整的,这比一般事件的表现力更好。
Timer/通知事件
再考虑下Timer。例如轮询,在一定的时间间隔监视某个值的场景。Rx将Timer变为一个序列,另外通过Select方法可以灵活变换输出。这些组合在一起,监视对象的值在一定时间间隔内会自动推送过来,非常容易操作。而且过滤值的处理也很简单,可以只在值发生变化时再接收这样就更简单了。
下面是Rx处理Timer在一定时间里生成一个值并且进行过滤的示例代码。
- // 每隔1秒监视一下watchTarget.Value的值
- var polling =
- Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1))
- .Select(_ => watchTarget.Value)
- .DistinctUntilChanged(); // 只有在值发生变化时才引发事件(polling)
- // FileSystemWatcher的Changed事件
- // 发生一次变化,会触发多个事件
- var watcher =
- new FileSystemWatcher("C:\\", "test.txt")
- { EnableRaisingEvents = true };
- // "对于1秒内连续发生的事件,进行过滤,只处理最后一个"
- // 变成一个相对更容易处理的对象
- var changed =
- Observable.FromEventPattern<FileSystemEventArgs>(
- watcher, "Changed")
- .Throttle(TimeSpan.FromSeconds(1)); // Throttle方法是只允许通过指定时间和指定值的内容(顾名思义:阀门)
UnitTest
Rx的特点是可以自由的处理事件,利用Rx可以解决本来很困难的事件的UnitTest。例如:3分30秒时生成一个数值"10",之后的4分0秒时生成一个数值"20",Rx能模拟这样的时间和值组合的事件。这些测试用的方法都在Microsoft.Reactive.Testing 程序集里(用NuGet "Rx-Testing")。关于Rx的UnitTest,将在后面再详细介绍。
FromEvent方法和FromEventPattern方法
用Rx处理事件,需要用 FromEvent 方法或者 FromEventPattern 方法将事件变为 IObservable<T> 对象。FromEvent 方法可以转换 Action<T> 代理,序列元素则为 T 。FromEventPattern 方法可以转换 EventHandler 代理,序列元素则为 EventPattern<TEventArgs>,它包装了 Object 类型的 sender 和 TEventArgs 类型的 e。
事件的Rx变换:指定事件名
先来看看 FromEventPattern 方法。
- // WPF/Silverlight/WP7 按钮控件(「button1」)的Click事件 Rx 化
- Observable.FromEventPattern<RoutedEventArgs>(button1, "Click");
事件的Rx变换:指定事件处理方法(非反射方法)
如果不使用反射进行Rx变换的话,可以按照下面的作法:
- // 第一个参数是事件响应处理的代理(这里 h => h.Invoke 是固定)
- // 第二个参数绑定事件,第三个参数解除事件
- Observable.FromEventPattern<RoutedEventHandler, RoutedEventArgs>(
- h => h.Invoke,
- h => button1.Click += h, h => button1.Click -= h);
如何清理事件的Rx变换
指定事件的处理,还有指定的字符串,这样的编码会比较多,实际运用中这样会影响代码的维护性。因此推荐将这些代码作为扩展方法分离出去。
- public static class ButtonBaseExtensions
- {
- // 分离的扩展方法
- public static IObservable<RoutedEventArgs> ClickAsObservable(this ButtonBase button)
- {
- return Observable.FromEventPattern<RoutedEventHandler, RoutedEventArgs>(
- h => h.Invoke,
- h => button.Click += h, h => button.Click -= h)
- .Select(x => x.EventArgs);
- }
- }
- // 实际运用时调用
- button1.ClickAsObservable().Subscribe(_ => MessageBox.Show("Clicked!"));
(Data Developer Center版) FromEvent 方法也和 FromEventPattern 方法类似,对象事件的类型是 Action<T> 。虽然 .NET Framework 中,Action<T> 虽然不是标准的Event对应的代理类型,直接使用的场景不是太多,但是对于开发者自定义的Action<T>的事件时可以使用。
另外,通过对代理的变换,就像上面的示例一样,也可以省略sender参数。
- public static class ButtonBaseExtensions
- {
- // 使用FromEvent生成EventPattern对象
- // 用Select省略sender
- public static IObservable<RoutedEventArgs> ClickAsObservable(this ButtonBase button)
- {
- return Observable.FromEvent<RoutedEventHandler, RoutedEventArgs>(
- h => (sender, e) => h(e),
- h => button.Click += h, h => button.Click -= h);
- }
- }
button1.ClickAsObservable().Select(ev => new { Sender = button1, EventArgs = ev })
接下来会进一步介绍 Rx 如何进行合成的。