如何使用 Observable.FromEvent 而不是 FromEventPattern 并避免字符串文字事件名称

2023-12-02

我正在学习 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 with Observable.FromEvent(以及路径SelectLINQ 查询来获取KeyChar)。其余的,包括Subscribe方法是相同的。但是,在运行时使用第二个解决方案我得到:

类型为“System.ArgumentException”的未处理异常发生于 mscorlib.dll

附加信息:无法绑定到目标方法,因为它 签名或安全透明度与 委托类型。

是什么导致了这个运行时异常?我应该如何避免它?

  • 图形用户界面:WinForms
  • Rx 和 Rx-WinForms 版本:2.1.30214.0(通过 Nuget)
  • 目标框架:4.5

Summary

首先要说明的是,您实际上并不需要使用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),但实际上事件可以与any委托类型(即使是具有返回类型的委托类型!)。我们通过将适当的委托传递给特殊函数来订阅事件,该函数将其添加到委托链(通常通过 += 运算符),或者如果尚未订阅处理程序,则委托将成为链的根。这就是为什么我们在引发事件时必须进行空检查。

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

让我们创建一个简单但非标准的 .NET 事件实现。这里我用的是不太常见的添加/删除语法来公开底层委托链并使我们能够记录订阅和取消订阅。我们的非标准事件具有一个带有整数和字符串参数的委托,而不是通常的委托object sender and 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);
        }
    }
}

回顾 Rx 订阅的工作原理

现在考虑 Observable 流是如何工作的。对可观察量的订阅是通过调用Subscribe方法并传递一个实现该方法的对象IObserver<T>接口,其中有OnNext, OnCompleted and OnError可观察对象调用的方法来处理事件。另外还有Subscribe方法返回一个IDisposable可用于取消订阅的句柄。

更典型的是,我们使用方便的扩展方法来重载Subscribe。这些扩展接受符合以下要求的委托处理程序OnXXX签名并透明地创建AnonymousObservable<T> whose 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处理程序。因此,FromEventcall 必须接受可用于在适当时间实现三个桥接操作的工厂方法。

FromEvent 类型参数

现在让我们考虑一下正确的实现FromEvent对于上述事件。

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

其实这可以是any您希望出现在目标可观察流中的类型。转换函数(稍后讨论)的工作是提供将事件调用转换为 OnNext 调用的逻辑 - 并且可以自由地决定如何发生这种情况。

在这里我们将绘制地图int x, string yBarEvent 调用的参数转换为描述这两个值的格式化字符串。换句话说,我们将调用fooInstance.RaiseBar(1, "a")导致调用someObserver.OnNext("X:1 Y:a").

这个例子应该可以消除一个非常常见的混乱根源:什么是类型参数FromEvent代表?这里是第一种类型BarHandler is 源 .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;
}

所以这个转换函数是工厂功能当调用时creates与底层 .NET 事件兼容的处理程序。工厂函数接受一个OnNext代表。该委托应由返回的处理程序调用,以响应使用底层 .NET 事件参数调用的处理程序函数。该委托将被调用,并将 .NET 事件参数转换为OnNext参数类型。所以从上面的例子我们可以看到工厂函数将被调用onNextHandler类型的Action<string>- 必须使用字符串值来调用它以响应每个 .NET 事件调用。工厂函数创建一个类型的委托处理程序BarHandler对于通过调用来处理事件调用的 .NET 事件onNextHandler使用从相应事件调用的参数创建的格式化字符串。

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

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

因此转换函数满足some事件订阅逻辑提供了一个函数来创建适当的事件处理程序,并且它还负责将 .NET 事件调用映射到 RxOnNext处理程序调用。

如前所述,存在超载FromEvent省略转换功能。这是因为如果事件委托已经与所需的方法签名兼容,则不需要OnNext.

添加/删除处理程序

剩下的两个参数是 addHandler 和 removeHandler,它们负责订阅和取消订阅创建的委托处理程序对实际 .NET 事件的处理 - 假设我们有一个实例Foo called 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);

由我们决定如何获取我们要桥接的事件 - 因此我们提供添加和删除处理程序函数,这些函数期望提供创建的转换处理程序。该事件通常是通过闭包捕获的,如上面的示例所示,我们关闭了foo实例。

现在我们已经拥有了所有的部分FromEventobservable 完全实现订阅、调用和取消订阅。

还有一件事...

最后还要提到一块胶水。 Rx 优化对 .NET 事件的订阅。实际上,对于任意给定数量的可观察对象订阅者,仅对底层 .NET 事件进行一次订阅。然后通过Publish机制。就好像一个Publish().RefCount()已被附加到可观察值中。

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

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

我确实有助于消除对这个复杂功能如何工作的任何挥之不去的困惑!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用 Observable.FromEvent 而不是 FromEventPattern 并避免字符串文字事件名称 的相关文章

  • 使用 Windbg 调试 .NET 转储

    我对 NET 进程进行了转储 使用 ma 选项和 procdump exe 中的高 CPU 触发器 并且希望在正在运行的线程中查看有关我的代码正在执行的操作的线索 我明白了 procdump ma c 65 s 2 n 3 service
  • 当向数据库表添加一列时,如何让 datagridview 显示更改?

    我在 Visual Studio 的项目中创建了一个数据集 该数据集指向我的数据库中的一个表 然后将 datagridview 控件绑定到它 现在我打开数据库并向数据库中的表添加另一列 然后我打开数据集并更新其配置以包含更改 然后我想更新
  • 如何更改表单中所有面板的背景颜色

    我的工具需要帮助 我已经尝试改变Color my Panel with ColorDialog但它不起作用我想全部改变颜色Panel in my Form 面板构造函数 Panel p new Panel 事件处理程序 private vo
  • 如何引用 dotNet Core 项目?

    我想将 dotNet Core 项目引用到 WPF 项目 我的 WPF 项目使用 v4 6 2 dotNet Framework 我的 dotNet Core 项目使用 v1 6 dotNet Standard 当我尝试引用它时 出现此错误
  • 实体框架 5 不清除导航属性

    我在 Entity Framework 5 中遇到了这个奇怪的问题 我在其中一个实体中有一个导航属性 我想将其设置为null 但由于某种原因 该属性只有在我第二次调用该属性时才会被清除 using var db new Entities v
  • C# 列表框 ObservableCollection

    我正在尝试使用 ListBox DataSource ObservableCollection 但是我不知道如何在 OC 更新时让列表框自动更新 我可以在 OC 上挂接 CollectionChanged 事件 但是我需要对列表框执行什么操
  • 接口中的私有成员

    是否可以在 NET 接口中创建私有成员 我听说现在可以了 但我的 IDE 拒绝了 public interface IAnimal void SetDefaultName string name ChangeName name privat
  • 当应用程序未聚焦时监听按键

    我有一个应用程序 C 4 0 WPF 它是隐藏的 可以通过单击系统托盘图标或我创建的其他框架 停靠在左侧和最上面的小框架 来显示 My customer wants to add a new way to display the appli
  • 那里有更好的 DateTime.Parse 吗? [关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 有谁知道有一个库 付费或免费 能够处理比 DateTime Parse 使用的更常见的日期时间格式 能够
  • 如何定义 Swagger UI 参数的默认值?

    我已将 Swagger Swashbuckle 集成到 NET Core 2 2 API 项目中 一切都很好 我的要求纯粹是为了方便 考虑以下 API 方法 public Model SomeEstimate SomeRequest req
  • 正则表达式基于组的不同替换?

    所以我对正则表达式比较陌生 并且做了一些练习 我正在玩一个简单的 混淆器 它只是寻找 dot or dot or at or at 不区分大小写 并且在匹配项之前或之后有或没有任意数量的空格 这是针对通常情况的 someemail AT d
  • .NET ReaderWriterLockSlim 问题

    有很多关于 ReaderWriterLockSlim 类的文章 它允许多次读取和一次写入 所有这些 至少我发现的 都告诉了如何使用它 而没有太多解释它为什么以及如何工作 标准代码示例是 lock EnterUpgradeableReadLo
  • C# winforms Toolstrip Dropdown with toolstripcontrolhost 第一次在 (0,0) 位置打开

    我有绑定导航器控件 其中包含 5 个项目 其中包括一个工具条向下按钮 我以编程方式添加一个包含面板的toolstripcontrolhost 到toolstripdownbutton 当我第一次打开下拉菜单时 它显示在位置 0 0 在第二次
  • EF Core 6.0 时态表 - 添加迁移 - 期间属性“Comment.PeriodStart”必须是影子属性

    我们最近将项目升级为Microsoft EntityFrameworkCore 6 0 0 此版本支持开箱即用的 SQL Server 时态表 https devblogs microsoft com dotnet prime your f
  • 使用.NET代码将系统时间同步到域控制器

    我要运行基于时间的测试 需要在测试期间多次更改系统时间 我希望能够在测试结束时将时间重新同步到域控制器时间 我有任何方法可以使用 NET 代码 C 来做到这一点 我正在使用以下位置的 p invoke 函数更改时间 使用 C 以编程方式设置
  • 我应该更喜欢 C# 中的静态方法吗

    在花了一些时间学习函数式编程之后 我越来越自然地想要使用不执行任何突变的静态方法 我有什么理由应该抑制这种本能吗 我觉得这个问题有点奇怪 因为静态方法和不执行突变的方法是方法的两个正交分类 您可以拥有可变静态方法和非可变实例方法 对我来说
  • 使用 POST 的 HttpWebRequest 的性能

    我有一个用于测试网络服务的小工具 它可以使用 POST 或 GET 调用 Web 服务 使用POST的代码是 public void PerformRequest WebRequest webRequest WebRequest Creat
  • 如何使用T4从一个模板同时生成两个文件?

    我遇到的情况是 我需要生成两个 CSharp 代码文件 它们的代码几乎相同 但方法的输入和输出类型的命名空间不同 事实上 每个文件都针对特定国家 地区 并且类型来自特定国家 地区的 WSDL 我正在围绕服务编写一些包装器 逻辑完全相同 但从
  • 在 C# 中解析 JS Date.toIsoString

    我需要将 JS 日期存储为 ISO 8601 日期 我目前正在从格式为 2019 06 22T00 00 00 000Z 的表单中获取日期 正如 JS 的 toIsoString 方法所期望的那样 当这个日期传递到我的 API 控制器时 我
  • 如何在Windows窗体中打开进程

    我想在我的 Windows 窗体应用程序中打开进程 例如 我希望当用户按下 Windows 窗体容器之一中的按钮时 mstsc exe 将打开 如果他按下按钮 它将在另一个容器上打开 IE DllImport user32 dll SetL

随机推荐

  • Room 为离线应用程序创建备份的最佳方法?

    所以我可以说正在使用非常复杂的数据库many to many数据库设计与foreign keys并连接表 它是Room数据库 我想为其创建备份系统 因为它是离线应用程序 我需要导出数据库并将其存储到谷歌驱动器的应用程序文件夹中 最近几天我读
  • 搜索按钮上的进度条

    我有这个 C 代码来显示进度条 public partial class FormPesquisaFotos Form public FormPesquisaFotos InitializeComponent private void Fo
  • switch 语句的逻辑部分或 case 部分如何?

    如果您有一个 switch 语句并希望在值为一个值时运行某些代码or另一个你怎么做 以下代码始终进入默认情况 include
  • Java Paths.get .... readAllBytes(path)) 不适用于相对路径

    我是 Java 新手 正在尝试构建 FX 应用程序 我的功能之一旨在用其他字符串替换某些字符串 只要我定义目标文件的绝对路径 该脚本就可以正常工作 但当我使用相对路径时 该脚本就会中断 问题出在 readAllBytes 方法中 该方法仅适
  • JPA - 可以在不提供可选参数的情况下调用存储过程吗?

    我正在调用带有许多可选参数的第三方存储过程 它位于 MS SQL Server 2008 R2 中 如下所示 procedure dbo pTest ReportDate varbinary max out Optional1 varcha
  • “?”附近的语法不正确: Nhibernate 生成的查询

    我在 Nhbernate 中使用位置参数时遇到问题 Criteria GroupProperty 正在发出带有命名变量和位置变量的 SQL 这个说法 session CreateCriteria typeof MatchStageFrom
  • 如何将 Matlab 函数的输出分配给单行中的变量?

    matlab中有没有一种方法可以将matlab中函数的输出分配给单行内的向量 例如 此函数应分配周长和面积值 function p a square geom side p perimeter square side a side 2 p
  • 分两列打印

    我们应该形成一个出现 108 次的名称数组 我们应该在左列中包含名称 1 54 在右列中包含名称 55 108 当一页有 108 个名称后 我们初始化数组并重新开始 我的代码的输出显示打印的名称 1 54 并且名称 55 108 不是位于同
  • Angular 2-ngModel 中的更改未反映在下拉列表中

    所以我有一个非常简单的场景 其中在下拉列表更改时显示确认弹出窗口 如果用户选择取消 我需要将下拉值恢复为旧值 我认为我做得正确 甚至在 DOM 中 ngModel 的值与select正在反映 但不知何故 它并没有恢复显示中的选定值 这是我的
  • 以编程方式获取框架 ID

    有人知道是否有办法以编程方式获取当前框架 ID 是的 您可以在 Play 类上获取该属性 Play id
  • 离散小波变换 Matlab

    我正在尝试使用 Matlab 小波工具箱中提供的函数来创建图像的多级离散小波分解 提取系数 操作它们 并将它们重新组合回图像中 我尝试使用许多函数 但它们似乎都不能满足我的需要 以下是执行此操作的步骤 使用wavedec2将图像分解为 C
  • 在 C# 中,这段带有“get”的代码是什么意思?

    我是 C 新手 private string m public string M get return m C 中的这种 getter setter 是否像 Java 一样 这部分是一个字段 private string m 这部分是一个只
  • 将数组/对象树的键转换为小写

    我目前正在优化一个 PHP 应用程序 发现一个函数被调用了大约 10 20k 次 所以我想我应该从那里开始优化 function keysToLower obj if is object obj is array obj return ob
  • 从列表中随机选择

    我在 Excel 工作表 A1 B115 中有一个项目列表 目前我可以输入 10 个变量 从列表中检索正确的数据 现在代码 C1 1 运行A1 A115并检查值是否在1000 2000之间 如果是这样 请将 B 值复制到某处 C2 1 运行
  • 如何在 WooCommerce 中对自定义购物车费用进行征税

    准确地找到了我正在寻找的代码片段 无论价格如何 都可以为每个单独的购物车项目添加固定费用金额 这个网站是卖轮胎的 所以每个轮胎都会充电3 这是我正在使用且有效的代码 add action woocommerce cart calculate
  • 无法维护java多线程中生产者任务的顺序

    我正在编写一个多线程应用程序 其中有 n 个生产者尝试将元素添加到共享资源 我想维护生产者在共享资源中生成元素的顺序 例如 我的共享资源是 SynchronizedQueue P1 P2 P3 P4 将按照 p1 p2 p3 p4 的顺序生
  • 修复“xml 外部实体引用不正确的限制”的最佳方法是什么?

    我们最近运行 VeraCode 指出了以下方法 public XmlElement RunProcedureXmlElement string Procedure List
  • 使用线程池/线程来读取大型文本文件?

    关于我之前的一个问题 我发布了 我必须读取几个非常大的 txt 文件 并且必须根据用户输入使用多个线程或单个线程来执行此操作 假设我有一个获取用户输入的 main 方法 并且用户请求一个线程并希望为该线程处理 20 个 txt 文件 我将如
  • GAE Webapp2 - 销毁会话不起作用

    或者我误解了如何破坏工作 这是一个示例代码 class TestHandler BaseHandler def get self counter self session get counter if not counter counter
  • 如何使用 Observable.FromEvent 而不是 FromEventPattern 并避免字符串文字事件名称

    我正在学习 WinForms 中的 Rx 方法 并有以下代码 Create an observable from key presses grouped by the key pressed var groupedKeyPresses Ob