如何使用Observable.FromEvent代替FromEventPattern并避免使用字符串文字事件名称

罩衫

我正在学习WinForms中有关Rx的方法,并具有以下代码:

// Create an observable from key presses, grouped by the key pressed
var groupedKeyPresses = Observable.FromEventPattern<KeyPressEventArgs>(this, "KeyPress")
                                  .Select(k => k.EventArgs.KeyChar)
                                  .GroupBy(k => k);

// Increment key counter and update user's display
groupedKeyPresses.Subscribe(keyPressGroup =>
{
    var numPresses = 0;
    keyPressGroup.Subscribe(key => UpdateKeyPressStats(key, ++numPresses));
});

这可以完美地工作/运行,在KeyPress事件中进行流传输,按按键进行分组,然后跟踪每个按键被按下了多少次,并UpdateKeyPressStats使用按键和新的按下次数来调用方法。装运它!

但是,FromEventPattern由于对事件的字符串文字引用,我不喜欢签名。所以,我想我会尝试FromEvent

// Create an observable from key presses, grouped by the key pressed
var groupedKeyPresses = Observable.FromEvent<KeyPressEventHandler, KeyPressEventArgs>(h => this.KeyPress += h, h => this.KeyPress -= h)
                                  .Select(k => k.KeyChar)
                                  .GroupBy(k => k);

// Increment key counter and update user's display
groupedKeyPresses.Subscribe(keyPressGroup =>
{
    var numPresses = 0;
    keyPressGroup.Subscribe(key => UpdateKeyPressStats(key, ++numPresses));
});

因此,唯一的更改是换出Observable.FromEventPatternObservable.FromEvent(和SelectLINQ查询中的路径以获取KeyChar)。其余Subscribe方法,包括方法都是相同的。但是,在运行第二种解决方案时,我得到了:

mscorlib.dll中发生了'System.ArgumentException'类型的未处理异常

附加信息:无法绑定到目标方法,因为其签名或安全性透明性与委托类型的签名或安全性透明性不兼容。

是什么导致此运行时异常,我应该如何避免呢?

  • 界面:WinForms
  • Rx和Rx-WinForms版本:2.1.30214.0(通过Nuget)
  • 目标框架:4.5
詹姆斯世界

概要

首先要说明的是,您实际上不需要使用它Observable.FromEvent来避免字符串文字引用。此版本的FromEventPattern适用于:

var groupedKeyPresses =
    Observable.FromEventPattern<KeyPressEventHandler, KeyPressEventArgs>(
        h => KeyPress += h,
        h => KeyPress -= h)
        .Select(k => k.EventArgs.KeyChar)
        .GroupBy(k => k);

如果您确实想FromEvent工作,可以这样进行:

var groupedKeyPresses =
    Observable.FromEvent<KeyPressEventHandler, KeyPressEventArgs>(
        handler =>
        {
            KeyPressEventHandler kpeHandler = (sender, e) => handler(e);
            return kpeHandler;
        }, 
        h => KeyPress += h,
        h => KeyPress -= h)
        .Select(k => k.KeyChar)
        .GroupBy(k => k);

为什么?这是因为FromEvent运算符存在于任何事件委托类型。

这里的第一个参数是将事件连接到Rx订户的转换函数。它接受观察者的OnNext处理程序(Action<T>),并返回与将调用该OnNext处理程序的基础事件委托兼容的处理程序。然后可以将此生成的处理程序订阅该事件。

我从不喜欢此功能的官方MSDN文档,因此这里是一个扩展的说明,逐步介绍了此功能的用法。

Observable.FromEvent的不足

以下内容细分了FromEvent存在原因及其工作方式:

回顾.NET事件订阅的工作方式

考虑.NET事件如何工作。这些被实现为委托链。标准事件委托遵循的模式delegate void FooHandler(object sender, EventArgs eventArgs),但实际上,事件可以使用任何委托类型(甚至那些具有返回类型的委托)!我们通过将适当的委托传递给特殊函数来订阅事件,该函数将其添加到委托链(通常通过+ =运算符),或者如果尚未订阅任何处理程序,则委托将成为链的根。这就是为什么在引发事件时必须做空检查的原因。

引发事件时,(通常)将调用委托链,以便依次调用链中的每个委托。要取消订阅.NET事件,将委托传递到一个特殊函数(通常通过-=运算符),以便可以将其从委托链中删除(链走直到找到匹配的引用,并且该链接为从链中删除)。

让我们创建一个简单但非标准的.NET事件实现。在这里,我使用的是不太常用的添加/删除语法来公开底层的委托链,并使我们能够记录订阅和取消订阅。我们的非标准事件设有一个整数的参数和串,而不是通常的委托object senderEventArgs亚类:

public delegate void BarHandler(int x, string y);

public class Foo
{  
    private BarHandler delegateChain;

    public event BarHandler BarEvent
    {
        add
        {
            delegateChain += value;                
            Console.WriteLine("Event handler added");
        }
        remove
        {
            delegateChain -= value;
            Console.WriteLine("Event handler removed");
        }
    }

    public void RaiseBar(int x, string y)
    {
        var temp = delegateChain;
        if(temp != null)
        {
            delegateChain(x, y);
        }
    }
}

回顾Rx订阅的工作方式

现在考虑可观察流的工作方式。订阅到可观察到的是通过调用形成Subscribe方法并传递一个对象,该工具的IObserver<T>接口,它具有OnNextOnCompletedOnError方法,通过可观察到的调用来处理事件。另外,该Subscribe方法返回一个IDisposable可以取消订阅句柄。

更通常地,我们使用重载的便捷扩展方法Subscribe这些扩展接受符合OnXXX签名的委托处理程序,并透明创建AnonymousObservable<T>OnXXX方法将调用这些处理程序的代理处理程序。

桥接.NET和Rx事件

那么,如何创建一个将.NET事件扩展到Rx可观察流中的桥?调用Observable.FromEvent的结果是创建一个IObservable,其Subscribe方法的作用类似于将创建此桥的工厂。

.NET事件模式不表示已完成或错误事件。仅发生了一个事件。换句话说,我们仅需桥接映射到Rx的事件的三个​​方面,如下所示:

  1. 订阅(例如,呼叫IObservable<T>.Subscribe(SomeIObserver<T>)映射到)fooInstance.BarEvent += barHandlerInstance
  2. 调用,例如调用barHandlerInstance(int x, string y)映射SomeObserver.OnNext(T arg)
  3. 取消订阅,例如,假设我们将调用返回的IDisposable处理程序保留Subscribe到变量中subscription,然后将subscription.Dispose()映射到fooInstance.BarEvent -= barHandlerInstance

请注意,只有调用Subscribe才能创建订阅。因此,该Observable.FromEvent调用返回的工厂支持对基础事件的订阅,调用和取消订阅。此时,没有事件订阅发生。只有在调用Subscribe时,观察者及其OnNext处理程序才可用因此,FromEvent调用必须接受工厂方法,该方法可用于在适当的时候实施三个桥接操作。

FromEvent类型参数

因此,现在让我们考虑FromEvent上述事件的正确实现

回想一下,OnNext处理程序仅接受一个参数。.NET事件处理程序可以具有任意数量的参数。因此,我们的第一个决定是选择一种类型来表示目标可观察流中的事件调用。

实际上,这可以是您希望在目标可观察流中显示的任何类型。转换功能(稍后讨论)的工作是提供将事件调用转换为OnNext调用的逻辑-并且有足够的自由来决定如何发生。

在这里,我们将把int x, string yBarEvent调用参数映射到描述两个值的格式化字符串中。换句话说,我们将导致调用fooInstance.RaiseBar(1, "a")以导致的调用someObserver.OnNext("X:1 Y:a")

该示例应消除一个非常常见的混淆源:FromEvent表示的类型参数是什么?这里的第一种类型BarHandler源.NET事件委托类型,第二种类型是目标OnNext处理程序的参数类型。由于第二种类型通常是EventArgs子类,因此通常假定它必须是.NET事件委托的某些必要部分-许多人都忽略了它的相关性实际上是由于OnNext处理程序而引起的事实因此,FromEvent呼叫的第一部分如下所示:

 var observableBar = Observable.FromEvent<BarHandler, string>(

转换功能

现在让我们考虑的第一个参数FromEvent,即所谓的转换函数。(请注意,FromEvent忽略了一些转换功能的重载-稍后将对此进行更多说明。)

由于类型推断,lambda语法可以被截断很多,因此下面是一个长手的版本:

(Action<string> onNextHandler) =>
{
    BarHandler barHandler = (int x, string y) =>
    {
        onNextHandler("X:" + x + " Y:" + y);
    };
    return barHandler;
}

因此,此转换函数是一种工厂函数,在调用时将创建与基础.NET事件兼容的处理程序。工厂函数接受OnNext委托。该委托应该由返回的处理程序调用,以响应使用基础.NET事件参数调用的处理程序功能。将.NET事件参数转换为OnNext参数类型的实例的结果将调用该委托因此,从上面的示例中我们可以看到,工厂函数将以onNextHandler类型Action<string>调用-必须响应每个.NET事件调用而使用字符串值来调用它。工厂函数创建类型的委托处理程序BarHandler通过onNextHandler使用从相应事件调用的参数创建的格式化字符串调用来处理事件调用的.NET事件

通过一些类型推断,我们可以将上面的代码折叠为以下等效代码:

onNextHandler => (int x, string y) => onNextHandler("X:" + x + " Y:" + y)

因此,转换函数在提供创建合适的事件处理程序的功能时满足了一些事件订阅逻辑,并且还完成了将.NET事件调用映射到RxOnNext处理程序调用的工作。

如前所述,重载FromEvent忽略了转换功能。这是因为如果事件委托已经与所需的方法签名兼容,则不需要这样做OnNext

添加/删除处理程序

剩下的两个参数是addHandler和removeHandler,它们负责订阅和取消订阅创建的委托处理程序到实际的.NET事件-假设我们有一个Foo被调用的实例,foo那么完成的FromEvent调用如下所示:

var observableBar = Observable.FromEvent<BarHandler, string>(
    onNextHandler => (int x, string y) => onNextHandler("X:" + x + " Y:" + y),
    h => foo.BarEvent += h,
    h => foo.BarEvent -= h);

由我们决定如何处理要桥接的事件-因此,我们提供了预期会提供给创建的转换处理程序的add和remove处理程序功能。该事件通常是通过闭包捕获的,如上例中我们在foo实例上关闭的那样

现在,我们可以FromEvent观察到所有部分,可以完全实现订阅,调用和取消订阅。

还有一件事...

最后一滴胶要提。Rx优化对.NET事件的订阅。实际上,对于可观察到的任何给定数量的订阅者,仅对基础.NET事件进行一次订阅。然后,通过该Publish机制将其组播到Rx用户好像Publish().RefCount()已经将a附加到了可观察对象上了。

考虑使用上面定义的委托和类的以下示例:

public static void Main()
{
    var foo = new Foo();

    var observableBar = Observable.FromEvent<BarHandler, string>(
        onNextHandler => (int x, string y)
            => onNextHandler("X:" + x + " Y:" + y),
    h => foo.BarEvent += h,
    h => foo.BarEvent -= h);

    var xs = observableBar.Subscribe(x => Console.WriteLine("xs: " + x));
    foo.RaiseBar(1, "First");    
    var ys = observableBar.Subscribe(x => Console.WriteLine("ys: " + x));
    foo.RaiseBar(1, "Second");
    xs.Dispose();
    foo.RaiseBar(1, "Third");    
    ys.Dispose();
}

这将产生以下输出,表明仅进行了一个预订:

Event handler added
xs: X:1 Y:First
xs: X:1 Y:Second
ys: X:1 Y:Second
ys: X:1 Y:Third
Event handler removed

我确实可以帮助您消除对这个复杂功能如何工作的任何长期困扰!

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

使用字符串代替属性名称(LINQ)

来自分类Dev

使用字符数组代替字符串

来自分类Dev

如何使用字符串以不同的顺序显示名称?

来自分类Dev

如何使用字符串的值作为对象的名称?

来自分类Dev

如何使用字符串访问Python字典名称?

来自分类Dev

如何使用字符串作为 SKScene 的名称

来自分类Dev

如何使用Observable Java

来自分类Dev

使用字符串代替PY_VAR

来自分类Dev

MongoDB:使用字符串代替BsonObjectId

来自分类Dev

Perl - 使用字符串代替哈希键

来自分类Dev

使用字符串索引数组避免类型安全?

来自分类Dev

如何使用 Observable 缓冲字符串流?

来自分类Dev

使用字符串与属性名称进行角度绑定

来自分类Dev

使用字符串名称访问对象成员

来自分类Dev

VBA:使用字符串变量设置范围名称

来自分类Dev

在不同功能中使用字符串名称

来自分类Dev

使用字符串作为属性名称

来自分类Dev

使用字符串名称选择对象?

来自分类Dev

使用字符串名称访问对象成员

来自分类Dev

使用字符串与属性名称进行角度绑定

来自分类Dev

在不同功能中使用字符串名称

来自分类Dev

标签名称不能使用字符串

来自分类Dev

使用字符串作为名称创建对象

来自分类Dev

仅使用字符串名称设置属性

来自分类Dev

使用字符串作为参数名称

来自分类Dev

使用 fle 使用字符串文字比较

来自分类Dev

如何在图像源中使用字符串代替url

来自分类Dev

在python dict中使用字符串值时如何避免KeyError

来自分类Dev

在StringBuffer追加中使用字符代替字符串作为单字符值