我正在学习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.FromEventPattern
了Observable.FromEvent
(和Select
LINQ查询中的路径以获取KeyChar
)。其余Subscribe
方法,包括方法都是相同的。但是,在运行第二种解决方案时,我得到了:
mscorlib.dll中发生了'System.ArgumentException'类型的未处理异常
附加信息:无法绑定到目标方法,因为其签名或安全性透明性与委托类型的签名或安全性透明性不兼容。
是什么导致此运行时异常,我应该如何避免呢?
首先要说明的是,您实际上不需要使用它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文档,因此这里是一个扩展的说明,逐步介绍了此功能的用法。
以下内容细分了FromEvent
存在原因及其工作方式:
考虑.NET事件如何工作。这些被实现为委托链。标准事件委托遵循的模式delegate void FooHandler(object sender, EventArgs eventArgs)
,但实际上,事件可以使用任何委托类型(甚至那些具有返回类型的委托)!我们通过将适当的委托传递给特殊函数来订阅事件,该函数将其添加到委托链(通常通过+ =运算符),或者如果尚未订阅任何处理程序,则委托将成为链的根。这就是为什么在引发事件时必须做空检查的原因。
引发事件时,(通常)将调用委托链,以便依次调用链中的每个委托。要取消订阅.NET事件,将委托传递到一个特殊函数(通常通过-=运算符),以便可以将其从委托链中删除(链走直到找到匹配的引用,并且该链接为从链中删除)。
让我们创建一个简单但非标准的.NET事件实现。在这里,我使用的是不太常用的添加/删除语法来公开底层的委托链,并使我们能够记录订阅和取消订阅。我们的非标准事件设有一个整数的参数和串,而不是通常的委托object sender
和EventArgs
亚类:
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);
}
}
}
现在考虑可观察流的工作方式。订阅到可观察到的是通过调用形成Subscribe
方法并传递一个对象,该工具的IObserver<T>
接口,它具有OnNext
,OnCompleted
和OnError
方法,通过可观察到的调用来处理事件。另外,该Subscribe
方法返回一个IDisposable
可以取消订阅的句柄。
更通常地,我们使用重载的便捷扩展方法Subscribe
。这些扩展接受符合OnXXX
签名的委托处理程序,并透明创建AnonymousObservable<T>
其OnXXX
方法将调用这些处理程序的代理处理程序。
那么,如何创建一个将.NET事件扩展到Rx可观察流中的桥?调用Observable.FromEvent的结果是创建一个IObservable,其Subscribe
方法的作用类似于将创建此桥的工厂。
.NET事件模式不表示已完成或错误事件。仅发生了一个事件。换句话说,我们仅需桥接映射到Rx的事件的三个方面,如下所示:
IObservable<T>.Subscribe(SomeIObserver<T>)
映射到)fooInstance.BarEvent += barHandlerInstance
。barHandlerInstance(int x, string y)
映射SomeObserver.OnNext(T arg)
IDisposable
处理程序保留Subscribe
到变量中subscription
,然后将subscription.Dispose()
映射到fooInstance.BarEvent -= barHandlerInstance
。请注意,只有调用Subscribe
才能创建订阅。因此,该Observable.FromEvent
调用返回的工厂支持对基础事件的订阅,调用和取消订阅。此时,没有事件订阅发生。只有在调用Subscribe
时,观察者及其OnNext
处理程序才可用。因此,FromEvent
调用必须接受工厂方法,该方法可用于在适当的时候实施三个桥接操作。
因此,现在让我们考虑FromEvent
上述事件的正确实现。
回想一下,OnNext
处理程序仅接受一个参数。.NET事件处理程序可以具有任意数量的参数。因此,我们的第一个决定是选择一种类型来表示目标可观察流中的事件调用。
实际上,这可以是您希望在目标可观察流中显示的任何类型。转换功能(稍后讨论)的工作是提供将事件调用转换为OnNext调用的逻辑-并且有足够的自由来决定如何发生。
在这里,我们将把int x, string y
BarEvent调用的参数映射到描述两个值的格式化字符串中。换句话说,我们将导致调用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] 删除。
我来说两句