如何使用Rx和F#将WPF Button.Click事件转换为Observable

How to convert a WPF Button.Click Event into Observable using Rx and F#

我正在尝试复制一些从Button.Click事件创建IObservable的C#代码。
我想将此代码移植到F#。

这是原始的C#代码,可正确编译:

1
2
3
4
Observable.FromEvent<RoutedEventHandler, RoutedEventArgs>(
                                    h => new RoutedEventHandler(h),
                                    h => btn.Click += h,
                                    h => btn.Click -= h))

这是我在F#中执行相同操作的失败尝试:

1
2
3
4
Observable.FromEvent<RoutedEventHandler, RoutedEventArgs>(
            Func<EventHandler<RoutedEventArgs>, RoutedEventHandler>(fun h -> RoutedEventHandler(h)),
            Action<RoutedEventHandler>(fun h -> btn.Click.AddHandler h),
            Action<RoutedEventHandler>(fun h -> btn.Click.RemoveHandler h))

除了语句的第二行之外,其他所有事情都很高兴。

F#编译器抱怨fun h -> RoutedEventHandler(h),因为
它不想将h用作RoutedEventHandler构造函数的参数。

另一方面,C#编译器似乎完全可以接受h => new RoutedEventHandler(h)

有趣的是,在两个代码示例(C#和F#)中,h的类型均为EventHandler

我从F#编译器收到的错误消息是:

Error 2 This expression was expected to have type obj -> RoutedEventArgs -> unit but here has type EventHandler

我在PresentationCore中找到的RoutedEventHandler的签名是:

public delegate void RoutedEventHandler(object sender, RoutedEventArgs e);

如您所见,它确实将objectRoutedEventArgs作为参数,因此F#编译器实际上是正确的。

C#编译器在幕后做一些魔术来使F#编译器没有做这项工作,或者我只是在这里遗漏了什么?

不管哪种方式,我如何在F#中使它起作用?


我知道从WPF Button.Click事件中制作IObservable<_>的最简单方法是强制转换:

1
2
3
4
5
open System
open System.Windows.Controls

let btn = new Button()
let obsClick = btn.Click :> IObservable<_>

正在检查obsClick ...

1
val obsClick : IObservable<Windows.RoutedEventArgs>

这是可能的,因为标准.NET事件的F#表示形式是IEvent(在这种情况下)。 从文档中可以看到,IEvent实现了IObservable。 换句话说,在F#中,每个事件都已经是IObservable


乔尔·穆勒(Joel Mueller)很有名,所以仅作记录:C#代码的直接翻译将是

1
2
3
4
5
Observable.FromEvent(
    (fun h -> RoutedEventHandler(fun sender e -> h.Invoke(sender, e))),
    (fun h -> b.Click.AddHandler h),
    (fun h -> b.Click.RemoveHandler h)
)