`
tianshibaijia
  • 浏览: 1125146 次
文章分类
社区版块
存档分类
最新评论

16.1.2 使用事件和观测

 
阅读更多
16.1.2 使用事件和观测






到目前为止,我们已经看到两种表示事件的类型,它们之间的关系相当简单: IEvent<'T> 接口继承自 IObservable<'T> 接口。IEvent<'T> 类型更具体,主要用于 F#,表示标准的 .NET 事件,而不是特殊的语言功能。


我们也看到了用高阶函数处理事件的 Observable 模块。模块中的函数,取 IObservable<'T> 接口值作为参数,但由于 IEvent<'T> 是从继承 IObservable<'T> ,我们也可以使用这些函数处理标准的 F# 事件。为了保持完整性,还有一个 Event 模块,其函数与 Observable 模块中的类似。这些函数的参数只是 IEvent<'T> 类型的值,产生相同的事件值类型。决定使用哪个接口和模块,听起来可能复杂,但我们可以使用四个简单的事实和规则进行决定:


1、IObservable<T> 有一个方法 Subscribe,它用于注册观察者(observer),当可观察对象的状态更改时,将被调用。


2、IEvent<T> 添加了几个方法,可能使用常见的编码模式,来处理标准事件。它提供了 Add 方法,很容易注册事件处理程序 AddHandler/RemoveHandler,当我们想要使用委托时,这是非常有用的。


3、当我们在 F# 中声明 C# 的兼容事件时,可以使用 IEvent<'T> 类型,将在 16.1.5 节中讨论。如果要构建一个事件,使用高阶函数从另一个事件,将需要使用该 Event 模块。


4、在本章的其余部分,我们会喜欢来自 Observable 的模块,因为这些函数更好地使用 Async.AwaitObservable 基元,在 16.3 节中我们会看到。组合 Event 模块的函数,使用我们稍后将介绍的方法,泄漏(leaks),其中,事件处理程序没有正确地删除。


现在,我们知道了将主要使用 Observable 模块,看一下表 16.1,展示了这个模块提供了哪些函数。Event 模块的结构很相似,除了类型签名。你可以看到,很多函数与处理序列的函数非常对应。






Table 16.1 Overview of the most important functions of the Observable module






函数
类型和描述

filter
('T -> bool) -> IObservable<'T> -> IObservable<'T>
当源事件发生时,返回被触发的事件,但仅是由这个事件所携带的值匹配指定的断言。这个函数对应于列表的 List.filter。
map
('T -> 'U) -> IObservable<'T> -> IObservable<'U>




每当源事件触发时,返回被触发的事件,由返回事件所携带的值,使用指定的函数计算源值。对应于 List.map 函数。

add
('T -> unit) -> IObservable<'T> –> unit


为指定的事件注册回调函数。每当发生该事件时,给定的函数调用。这个函数类似于 List.iter。

scan
('U -> 'T -> 'U) -> 'U -> IObservable<'T> -> IObservable<'U>


这个函数创建一个有内部状态的事件。初始状态由第二参数值指定,每当源事件使用指定的函数发生时,就更新。返回的事件报告每次源事件被触发时累加的状态,使用源事件的值,重新计算它之后。

merge
IObservable<'T> -> IObservable<'T> -> IObservable<'T>


作为参数传入的不管哪一个事件发生时,创建一个被触发的事件。注意,由事件 ('T) 携带的值的类型,必须与两个事件相同。

partition
('T -> bool) -> IObservable<'T>
-> IObservable<'T> * IObservable<'T>




把一个事件拆分为两个不同的事件,基于提供的断言。当输入的事件触发时,partition 函数运行断言,触发两个创建的事件之一,取决于断言的结果。行为对应于 List.partition 函数。







表 16.1 中的大部分函数应该很容易理解,因为,我们已经见过类似的处理列表的函数,唯一的区别是 Observable.scan,它看上去比其他的复杂些。处理列表的也存在 scan 函数,但我们尚未讨论,签名也类似于 List.fold,我们已经知道得很好。


这两个函数取一个初始状态和一个函数,它知道如何从原始状态计算新的状态,并从由事件所携带的列表或值计算一个元素。不同之处在于,fold 数返回列表的所有元素的累计结果。对事件来说,这是不可能的,因为我们不知道事件最后什么时候发生。不是等待最后一个元素,Observable.scan 函数返回一个触发的事件,每当内部状态重新计算时,计算到目前为止的聚合值。我们会在 16.1.3 节看到这样一个示例函数。


处理事件使用高阶函数的最大好处,是我们可以用更声明的方式写代码。在我们的示例中,替换一个命令 if,在有筛选器的事件处理程序中,但我们可以看更进一步的示例。如果我们创建一个函数(我们将称它 formatFileEvent),设置由 RenamedEventArgs 所携带的信息的格式,我们可以把整个事件处理写成一个单独的、简洁的表达式,如下所示。






Listing 16.3 Declarative event handling (F#)






fileWatcher.Renamed
|> Observable.filter isNotHidden
|> Observable.map formatFileEvent
|> Observable.add (printfn "%s")






清单 16.3 实现的功能,与清单 16.2 相同,使用两个辅助函数,和来自 Observable 模块的函数。一旦开始把一个事件当作一系列的值,代码就容易理解了。不用命令式指定当事件发生时应该做什么,而是以声明方式指定所需的结果方面。第一行指定哪种事件是我们感兴趣的,第二行指定哪些信息是重要的,最后一行显示格式化的信息。


最初,我们从一个携带 RenamedEventArgs 类型值的事件开始。筛选操作不更改类型,在某些情况下,不触发返回的事件。映射可以改变类型,formatFileEvent 函数返回一个字符串值,因此,我们最后的事件包含字符串。这意味着,最后的基元期望的函数,将一个字符串作为参数,并返回 unit。可以看到,可以很容易地创建这样一个函数,使用偏函数应用。带 %s 格式说明符的 printfn 函数,创建了打印给定字符串的函数。


这种处理事件的方式还带给我们更丰富的方式,将代码拆分为可重用的元素。我们可以省略最后一行,以创建一个事件,可以在应用程序中的其他地方使用。然后,用 Observable.add 与 MessageBox.Show 一起,以图形方式显示通知,用同一个事件追加日志文件。让我们看一个稍微复杂的例子。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics