F#中的事件(上)

简介:

.NET中事件的应用

事件是.NET中一个非常重要的概念。在WinForm和WebForm的开发中,事件的使用是极为常见的。比如下面这个简单的例子:

复制代码
C# Code - 添加事件处理函数
[STAThread]
static void Main()
{
    Form form 
= new Form()
    {
        Text 
= "Click Example",
        TopMost 
= true,
        Visible 
= true
    };

    EventHandler handler 
= new EventHandler(form_Click);
    form.Click 
+= handler;

    form.Show();
    Application.Run(form);
}

static void form_Click(object sender, EventArgs e)
{
    MessageBox.Show(
"Clicked me!");
}
复制代码


这里为窗体的Click事件添加了一个处理函数,此过程是通过委托完成的。我们知道,通过+=操作符可以添加一个处理函数,而通过-=则可以移除处理函数。在F#可以这么做:

复制代码
F# Code - 添加事件处理函数
open System
open System.Windows.Forms

let form_Click (sender: obj) (e: EventArgs) =
    MessageBox.Show(
"Clicked me!"|> ignore
  
let form = new Form(
            Text = 
"Click Example",
            TopMost = 
true,
            Visible = 
true)

let handler = new EventHandler(form_Click)
form.Click.AddHandler handler

form.Show()

[<STAThread
>]
do Application.Run(form)
复制代码


可以看到,添加事件处理函数的方式大致相同,即通过AddHandler方法(对应的,也存在一个RemoveHandler方法来移除处理函数)。这种方式是相当“命令式”的。

理解IEvent接口

现在来看看上面的例子,form.Click的信息是:

F# Signature Info
val it : IEvent<EventHandler,EventArgs> = FSI_0003+it@5


但它应该是:

C# Signature Info
public event EventHandler Click;


才对啊!事实上,F#自动将事件转换为了IEvent<EventHandler, EventArgs>,IEvent则继承了IDelegateEvent。它们的基本信息是:

复制代码
F# Signature Info
type IDelegateEvent<'del when 'del :> System.Delegate > =
    
abstract AddHandler'del -> unit
    abstract RemoveHandler'del -> unit

type IEvent<'Del,'when 'Del : delegate<'T,unit> and 'del :> System.Delegate > =
    abstract Add: event:('T -> unit) -> unit
    inherit IDelegateEvent<'del>
复制代码


IDelegateEvent提供了前面提到的AddHandler和RemoveHandler方法,它们的用法跟C#中的+=、-=操作符类似。IEvent的Add方法看起来有些奇怪,它是这样使用的:

F# Code - IEvent.Add方法
form.Click.Add(fun _ -> MessageBox.Show("Clicked me again!"|> ignore)


到这里,我们可以了解到在F#中如何以“命令式”的方式添加和移除事件处理函数了,现在你一定想知道F#还有什么是独特而吸引人的呢?

事件是可组合的一等公民

这句话可以分为两部分来理解,首先IEvent的出现使得事件成为了一等公民,也就是说,可以像其它值那样进行传递;然后,多个事件可以进行组合处理。

要做到这两点,我们要借助于Event模块(module)的一些组合子(combinator)成员,比如:

  • filter:对指定事件进行“过滤”,返回一个新的事件,新事件仅在条件满足时才会触发;
  • listen:侦听事件,在事件触发时执行指定函数;
  • map:根据指定的函数对事件进行“转换”,返回一个新的事件。

先来看看这三个方法组合起来的作用:

F# Code - Event基本组合函数
form.MouseDown
    
|> Event.map (fun args -> (args.X, args.Y))
    
|> Event.filter (fun (x, y) -> x > 100 && y > 100)
    
|> Event.listen (fun (x, y) -> printfn "(%d, %d)" x y)


MouseDown的第二个参数是MouseEventHandler类型,但现在我们只关心它的两个坐标值,于是使用map方法对该事件进行转换;到了filter方法这里,我们不希望该事件总是触发,进行过滤,只有当横纵坐标都大于100时才会触发;最后使用listen方法指定事件处理函数。整个过程非常清晰。

  • choose:一定程度上可以理解为filter和map的组合。
复制代码
F# Code - Event.choose方法
form.MouseDown
    
|> Event.choose (fun args ->
        
if args.X > 100 && args.Y > 100 then Some(args.X, args.Y)
        
else None)
    
|> Event.listen (fun (x, y) -> printfn "(%d, %d)" x y)
复制代码


这段代码的功能跟上段代码是一样的。现在来看一个更有意思的方法merge:

  • merge:将两个事件“合并”在一起。
复制代码
F# Code - Event.merge方法
form.MouseDown
    
|> Event.merge form.MouseMove
    
|> Event.filter (fun args -> args.Button = MouseButtons.Left)
    
|> Event.map (fun args -> (args.X, args.Y))
    
|> Event.listen (fun (x, y) -> printfn "(%d, %d)" x y)
复制代码


当发生了MouseDown或者MouseMove,并且按下的是左键时就会执行指定的函数。在F#中,我们不仅可以合并两个事件,还可以将一个事件进行分解:

  • partition:通过指定的谓词函数将事件分解为两个事件,当谓词函数返回true时,触发第一个事件,否则触发第二个事件。
复制代码
F# Code - Event.partition方法
let (overEvent, underEvent) =
    form.MouseMove
        
|> Event.merge form.MouseDown
        
|> Event.filter (fun args -> args.Button = MouseButtons.Left)
        
|> Event.map (fun args -> (args.X, args.Y))
        
|> Event.partition (fun (x, y) -> x > 100 && y > 100)
      
overEvent 
|> Event.listen (fun (x, y) -> printfn "Over (%d, %d)" x y)
underEvent 
|> Event.listen (fun (x, y) -> printfn "Under (%d, %d)" x y)
复制代码


如果横纵坐标都大于100的话,将触发overEvent,否则就会触发underEvent。

小结

在.NET开发中,事件以及基于事件的编程是颇为重要的概念,它得到了该平台下各种语言的支持。而F#将事件视为一等公民,这个特性使得我们可以通过更为有趣的方式对事件进行组合处理。

参考

First Class Composable Events in F#


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

目录
相关文章
|
机器学习/深度学习 存储 数据采集
数据分析案例-基于多元线性回归算法预测学生期末成绩
数据分析案例-基于多元线性回归算法预测学生期末成绩
1872 0
数据分析案例-基于多元线性回归算法预测学生期末成绩
|
存储 NoSQL 关系型数据库
PostgreSQL列存扩展hydra简单测试
Hydra是一款PostgreSQL的扩展,为PostgreSQL增加了列存引擎,使得PostgreSQL的olap性能大幅提升,本文介绍Hydra基本的使用方法。
|
11月前
|
关系型数据库 MySQL API
|
Java 数据库连接 Maven
【Spring】掌握 Spring Validation 数据校验
【Spring】掌握 Spring Validation 数据校验
450 0
|
监控 网络协议 UED
通过云拨测对指定服务器进行Ping/DNS监测
本实验将通过云拨测对指定服务器进行Ping/DNS监测,评估网站服务质量和用户体验。
568 109
|
安全 机器人 程序员
Windows 计划任务每天隔 3 小时运行一次批处理文件详细配置步骤
Windows 计划任务每天隔 3 小时运行一次批处理文件详细配置步骤
1849 2
|
缓存 安全 Java
Java并发编程中的线程安全性探讨
【2月更文挑战第6天】在Java开发中,多线程编程是一种常见的方式,然而如何确保线程安全性却是一个复杂且关键的问题。本文将深入探讨Java并发编程中的线程安全性,包括线程安全性的概念、常见的线程安全性问题以及解决方法,旨在帮助开发者更好地理解和应对多线程环境下的挑战。
|
安全 Go
gRPC(七)进阶:自定义身份验证
gRPC为每个gRPC方法调用提供了Token认证支持,可以基于用户传入的Token判断用户是否登陆、以及权限等,实现Token认证的前提是,需要定义一个结构体,并实现credentials.PerRPCCredentials接口。
1595 1
gRPC(七)进阶:自定义身份验证
|
SQL Oracle 关系型数据库
DBeaver同时执行多条insert into报错处理
DBeaver同时执行多条insert into报错处理
DBeaver同时执行多条insert into报错处理
|
网络协议 安全 物联网
常见物联网操作介绍
常见物联网操作介绍
308 0