目录:网上冲浪指南

FSharp 中被生成的 IEvent

2021/07/06
内容导航

为了方便 FSharp 用户以更加函数式的方式与 C# 的事件进行交互,F# 提供了 IEvent 类型。IEvent 继承了 IObservable,方便 F# 用户使用 Rx.Net 来处理事件流,另一方面,IEvent 还继承了 IDelegateEvent 方便用户将 F# 函数注册为事件的监听器。

除此之外,F# 还会将系统类库、第三方类库中的 CLIEvent 转换成对应类型的 IEvent,这个过程自动地发生在编译的过程中。接下来我将用一段非常简单的代码(代码来源)来演示其中的玄机。

在 fsi 中执行
type MyClassWithCLIEvent() =

    let event1 = new Event<_>()

    [<CLIEvent>]
    member this.Event1 = event1.Publish (1)

    member this.TestEvent(arg) =
        event1.Trigger(this, arg)

let classWithEvent = new MyClassWithCLIEvent()
<@ classWithEvent.Event1 @> (2)
  1. 声明一个 CLIEvent

  2. 使用 Quotation 获取 F# 表达式

执行结果如下
Quotations.Expr<IEvent<Handler<MyClassWithCLIEvent * obj>,
                         (MyClassWithCLIEvent * obj)>> =
  Call (None, CreateEvent,  (1)
      [Lambda (eventDelegate, Call (Some (PropertyGet (None, classWithEvent, [])), add_Event1, [eventDelegate])), (2)
       Lambda (eventDelegate,
               Call (Some (PropertyGet (None, classWithEvent, [])), remove_Event1, [eventDelegate])), (3)
       Lambda (callback,
               NewDelegate (FSharpHandler`1, delegateArg0, delegateArg1,
                            Application (Application (callback, delegateArg0),
                                         delegateArg1)))]) (4)
  1. 调用 RuntimeHelper.CreateEvent()

  2. CreateEvent 的第一个参数:一个 lambda,可以将事件委托添加为 classWithEvent.Event1 的监听器

  3. CreateEvent 的第二个参数:一个 lambda,将事件委托从 classWithEvent.Event1 的监听器中移除

  4. CreateEvent 的第三个参数:一个 lambda,将一个 F# 函数转换为 Event1 的委托类型

可以看到,当我们使用 F# 访问 CLIEvent 的时候,编译器会生成一个 IEvent 对象,并在 IEvent 的事件处理函数闭包中捕获 CLIEvent 所属的实例。编译器的这种行为有时候就让一些奇怪的代码正常运行:

let nullObj = Unchecked.defaultof<MyClassWithCLIEvent>
nullObj.Event1.GetType() // 一切正常
nullObj.Event1.Add(ignore) // NullReferenceException

在 F# 中仅访问 IEvent 类型的属性不会出现空引用异常,只有在注册、移除事件监听器的时候才会触发。

内容导航

本网站所展示的文章由 Zeeko Zhu 采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可

Zeeko's blog, Powered by ASP.NET Core 🐳