F#中的事件(下)

简介:

上一篇随笔简单介绍了在F#中事件的有趣特性,即可组合的一等公民,在我们处理事件时就拥有了更好的灵活性。在里面的例子中可以看到如何订阅一个事件,包括在C#/VB.NET中定义的标准.NET事件。本文将介绍如何在F#中发布(创建)和订阅自定义事件。

在C#中发布和订阅事件

在此之前,先来看看如何在C#中发布一个事件,这里将只考虑标准的.NET事件(即微软推荐的事件定义方式)。这个过程涉及两个重要的类型:EventArgs,作为包含事件数据的类的基类;EventHandler<TEventArgs>,表示将处理事件的方法。比如下面的例子:

复制代码
C# Code - 发布和订阅事件
public class KeyEventArgs : EventArgs
{
    
private readonly char keyChar;
    
public KeyEventArgs(char keyChar)
    {
        
this.keyChar = keyChar;
    }

    
public char KeyChar
    {
        
get { return keyChar; }
    }
}

public class KeyInputMonitor
{
    
public event EventHandler<KeyEventArgs> KeyDown;

    
protected virtual void OnKeyDown(KeyEventArgs e)
    {
        EventHandler
<KeyEventArgs> temp = KeyDown;

        
if (temp != null)
        {
            temp(
this, e);
        }
    }

    
public void SimulateKeyDown(char key)
    {
        KeyEventArgs e 
= new KeyEventArgs(key);
        OnKeyDown(e);
    }
}

internal class UsingCustomEvent
{
    
private static void Main()
    {
        KeyInputMonitor monitor 
= new KeyInputMonitor();
        monitor.KeyDown 
+= new EventHandler<KeyEventArgs>(monitor_KeyDown);

        monitor.SimulateKeyDown(
'A');
        monitor.SimulateKeyDown(
'V');
    }

    
static void monitor_KeyDown(object sender, KeyEventArgs e)
    {
        Console.WriteLine(e.KeyChar);
    }
}
复制代码


这里通过KeyEventArgs类来封装事件所包含的数据,然后以泛型EventHandler委托发布KeyDown事件,最后定义SimulateKeyDown方法来触发事件。在类型外部(这里是UsingCustomEvent类)就可以订阅该事件。

使用Event.create方法创建事件

事件的发布-订阅过程大致如此,理解了这个过程就比较容易理解F#的定义方式了。这里还是要用到那个Event模块,它的create方法将帮助我们创建事件,其签名信息为:

F# Signature Info
val it : (unit -> ('a -> unit) * IEvent<'a>)


该方法的返回值是一个tuple,一个用于触发事件的方法,另一个则是事件本身,就像前面例子中的SimulateKeyDown方法和KeyDown事件。比如:

复制代码
F# Code - 使用Event.create方法创建事件
let fire, event = Event.create()
event.Add(printfn 
"Fired %d")
fire(
25) // 
Fired 25

val fire : (int -> unit)
val event : IEvent<int>
复制代码


这里我们可以再一次见识类型推导的威力。不过问题是,以这样的方式创建事件后,事件是局部的,如何将事件封装在类中呢?

在F#中发布和订阅事件

现在将尝试在F#重写上面的C#示例。

复制代码
F# Code - 发布和订阅事件 
open System

type KeyEventArgs(keyChar: char) =
    
inherit EventArgs()
    
member this.KeyChar = keyChar
   
type KeyInputMonitor() =
    
let (keyDownFire, keyDownEvent: IEvent<KeyEventArgs>) = Event.create()
   
    
member this.KeyDown = keyDownEvent
    
member this.SimulateKeyDown key =
        
let args = new KeyEventArgs(key)
        keyDownFire args
   
let monitor = new KeyInputMonitor()
monitor.KeyDown.Add(
fun args -> printfn "%A" args.KeyChar)
monitor.SimulateKeyDown 
'A' 
monitor.SimulateKeyDown 
'V'  
复制代码


这里,在KeyInputMonitor发布KeyDown事件后,之后订阅和触发过程跟C#的例子很接近了。现在考虑在C#中使用F#中发布的事件。

发布标准的.NET的事件

现在我们在C#项目中引用上面创建的F#类,并尝试以常规的方式使用它的事件,就会发现难以使用。原因是它的KeyDown事件是IEvent类型的,而不是常规的委托类型。这个问题解决起来不难:

复制代码
F# Code - 发布标准的.NET事件
open System

type KeyEventArgs(keyChar: char) =
    
inherit EventArgs()
    
member this.KeyChar = keyChar
    
type KeyDownEventHandler = delegate of obj * KeyEventArgs -> unit
    
type KeyInputMonitor() =
    
let keyDownEvent = new Event<KeyDownEventHandler, KeyEventArgs>()
    
    [<CLIEvent
>]
    
member this.KeyDown = keyDownEvent.Publish
    
    
member this.SimulateKeyDown(key) = 
        keyDownEvent.Trigger(this, 
new KeyEventArgs(key))
        
let monitor = new KeyInputMonitor()
monitor.KeyDown.Add(
fun args -> printfn "%A" args.KeyChar)
monitor.SimulateKeyDown 
'A'
monitor.SimulateKeyDown 
'V'
复制代码


这里定义了一个委托KeyDownEventHandler,并且在创建事件的时候创建了一个Event类型的实例,而不是Event.create方法,更重要的是对事件属性添加了attribute:CLIEvent,这个attribute将使得这个事件属性编译为标准的.NET事件。现在来看,一方面在F#中使用该事件时与前面相同,另一方面在C#中使用时:

复制代码
C# Code - 使用在F#中发布的事件
internal class UsingFsEvent
{
    
private static void Main()
    {
        FsLib.KeyInputMonitor monitor 
= new FsLib.KeyInputMonitor();
        monitor.KeyDown 
+= new FsLib.KeyDownEventHandler(monitor_KeyDown);
        monitor.SimulateKeyDown(
'A');
        monitor.SimulateKeyDown(
'V');
    }

    
static void monitor_KeyDown(object sender, FsLib.KeyEventArgs e)
    {
        Console.WriteLine(e.KeyChar);
    }
}
复制代码

这个也与我们在C#中的使用习惯相同,这样在F#发布的事件就可以兼容于其它.NET语言了。

小结

本文首先介绍了如何使用Event.create方法创建新的事件,然后在此基础上讨论了如何发布和订阅事件,这样可以更符合我们的编码习惯。不过这种方式发布的事件在C#等其它.NET语言中却难以使用,所以最后介绍了如何发布标准的.NET事件,这样就可以兼容于F#和其它的.NET语言了。

参考

F# First Class Events – Creating Events
How to create .NET-compatible events in F#
《Expert F#》 by Don Syme , Adam Granicz , Antonio Cisternino


本文转自一个程序员的自省博客园博客,原文链接:http://www.cnblogs.com/anderslly/archive/2009/08/21/composable-events-in-fsharp-part2.html,如需转载请自行联系原作者。

目录
相关文章
|
2月前
|
JavaScript 前端开发
事件8
事件8
33 2
|
2月前
|
监控 JavaScript 前端开发
事件
事件
42 1
|
5月前
|
API
(22):事件总结
(22):事件总结
|
5月前
(18):事件
(18):事件
|
6月前
GotFocus和PreviewLeftButtonDown事件
GotFocus和PreviewLeftButtonDown事件
|
存储 JSON 前端开发
EventSource 引发的一系列事件 #150
EventSource 引发的一系列事件 #150
238 0
|
安全 C#
C#——事件
C#——事件
99 1
|
API 数据库
9.2领域事件
领域(近似理解为实现某个功能的多个模型)事件可以切断领域模型之间的强依赖关系,事件发布后,由事件的处理者决定如何响应事件,以便于实现事件发布和事件处理的解耦。
|
C#
C# 事件
C# 事件
114 0
C# 事件
|
Web App开发 缓存 JavaScript
55、其他常见事件
beforeunload事件在窗口、文档、各种资源将要卸载前触发。它可以用来防止用户不小心卸载资源。
178 0