16.1 使用事件有反应的编程
在这一点上,你知道如何在 C# 中,写响应事件的应用程序,我们已经看到,同样的技术也可以用在 F# 中。这种模式是为事件注册一个回调函数(或方法),在事件发生时,回调函数执行。这可以响应事件,也许更新了应用程序的状态,或改变了用户界面。
让我们先用一个例子回顾一下这种方法。清单 16.1 的代码,使用 FileSystemWatcher 类,监视文件系统中的更改。初始化以后,每当创建、重命名或删除文件时,监视程序都会触发事件。
Listing 16.1 Monitoring filesystem events (F#)
open System.IO
let fileWatcher = new FileSystemWatcher(@"C:\Test")
fileWatcher.EnableRaisingEvents <- true
let isNotHidden(fse:RenamedEventArgs) =
let hidden = FileAttributes.Hidden
(File.GetAttributes(fse.FullPath) &&& hidden) <> hidden
fileWatcher.Renamed.Add(fun fse –>
if isNotHidden(fse) then
printfn "%s renamed to %s" fse.OldFullPath fse.FullPath)
清单 16.1 首先为文件夹创建 FileSystemWatcher 对象,在其中,可以轻松地创建和重命名一些文件来测试代码。接下来的几行代码显示,是一个简单的函数,检查文件是否未标记为隐藏。给这个函数的参数是一个从 EventArgs 派生的类,携带有关由这个监视程序所触发事件的信息。我们将使用此函数,演示在声明式事件处理中的一个有趣的功能。我们使用二元和运算符 (&&&)来检查是否设置了 .NET 枚举类型的一个标志。
代码的最后部分注册了一个事件处理程序,当重命名文件时调用。相比其他 .NET 语言,F# 表示事件的方式不同。在 C# 中,事件是类的一个特殊成员,你只能通过添加(+=)或移除(-=)事件处理程序的运算符,来使用它。在 F# 中,是作为 IEvent <'T> 类型的标准成员,其中 T 参数指定这个事件(通常从 EventArgs 派生)所携带的值。此类型有一个 Add 方法,我们可以用来注册回调函数。它还支持方法调用 AddHandler 和 RemoveHandler,因此,你还可以使用委托,如果要能够在以后删除已注册的回调。
清单 16.1 中的示例使用 Add 方法,参数为 lambda 函数。该 lambda 函数只有一个参数,不包括通常的"发送者(sender)"的参数。原因是在 F# 中,可以轻松地访问发送者对象,在一个闭包中捕获它,因此,没有必须显式传递参数。回调通过打印有关重命名文件信息,响应事件,但我们不希望对每个事件都作出反应。相反,我们想要仅在受影响的文件未标记为隐藏时,才显示消息。为此,我们在回调函数内写一个 if 条件。
这工作也很好,但 F# 允许我们写更声明式的事件筛选,使程序更易于阅读,促进清晰的关系分离。稍后,我们会看到,至少在某种程度上,同样的原则也可以在 C# 中使用。
------------
[1] “Don’t call us, we’ll call you.”