与 lambda 一起使用的弱事件处理程序模型

2023-12-05

好的,所以这更像是一个答案而不是一个问题,但在询问之后这个问题,并将来自的各个部分组合在一起达斯汀·坎贝尔, Egor,还有来自“的最后一个提示”IObservable/Rx/Reactive 框架',我想我已经为这个特定问题找到了一个可行的解决方案。它可能会被 IObservable/Rx/Reactive 框架完全取代,但只有经验才能证明这一点。

我故意创建了一个新问题,以便给我空间来解释我如何得到这个解决方案,因为它可能不会立即显而易见。

有很多相关的问题,大多数都告诉您,如果您希望稍后能够分离它们,则不能使用内联 lambda:

  • .Net 中的弱事件?
  • 在 C# 中使用 lambda 解除事件挂钩
  • 使用 lambda 作为事件处理程序会导致内存泄漏吗?
  • 如何取消订阅使用 lambda 表达式的事件?
  • C# 中取消订阅匿名方法

确实,如果YOU如果希望稍后能够分离它们,您需要保留对 lambda 的引用。但是,如果您只是希望事件处理程序在您的订阅者超出范围时自行分离,那么这个答案适合您。


'答案

(如果您想了解我如何找到这个解决方案,请阅读下面的更多内容)

用法,给定一个香草对照MouseDown事件,以及具体的EventHandler<ValueEventArgs> ValueEvent event:

// for 'vanilla' events
SetAnyHandler<Subscriber, MouseEventHandler, MouseEventArgs>(
    h => (o,e) => h(o,e), //don't ask me, but it works*.
    h => control.MouseDown += h,
    h => control.MouseDown -= h,
    subscriber,
    (s, e) => s.DoSomething(e));  //**See note below

// for generic events
SetAnyHandler<Subscriber, ValueEventArgs>(
    h => control.ValueEvent += h,
    h => control.ValueEvent -= h,
    subscriber,
    (s, e) => s.DoSomething(e));  //**See note below

(*这是一个解决方法Rx)

(** 避免在这里直接调用订阅者对象非常重要(例如,如果我们在 Subscriber 类中,则放置subscriber.DoSomething(e),或者直接调用 DoSomething(e)。这样做有效地创建了对订阅者的引用,该引用完全击败物体...)

Note:在某些情况下,这可以在内存中留下对为 lambda 创建的包装类的引用,但它们只计算字节,所以我不太担心。

执行:

//This overload handles any type of EventHandler
public static void SetAnyHandler<S, TDelegate, TArgs>(
    Func<EventHandler<TArgs>, TDelegate> converter, 
    Action<TDelegate> add, Action<TDelegate> remove,
    S subscriber, Action<S, TArgs> action)
    where TArgs : EventArgs
    where TDelegate : class
    where S : class
{
    var subs_weak_ref = new WeakReference(subscriber);
    TDelegate handler = null;
    handler = converter(new EventHandler<TArgs>(
        (s, e) =>
        {
            var subs_strong_ref = subs_weak_ref.Target as S;
            if(subs_strong_ref != null)
            {
                action(subs_strong_ref, e);
            }
            else
            {
                remove(handler);
                handler = null;
            }
        }));
    add(handler);
}

// this overload is simplified for generic EventHandlers
public static void SetAnyHandler<S, TArgs>(
    Action<EventHandler<TArgs>> add, Action<EventHandler<TArgs>> remove,
    S subscriber, Action<S, TArgs> action)
    where TArgs : EventArgs
    where S : class
{
    SetAnyHandler<S, EventHandler<TArgs>, TArgs>(
        h => h, add, remove, subscriber, action);
}

细节

我的出发点是Egor的出色答案(请参阅带评论的版本链接):

public static void Link(Publisher publisher, Control subscriber) {
    var subscriber_weak_ref = new WeakReference(subscriber);
    EventHandler<ValueEventArgs<bool>> handler = null;
    handler = delegate(object sender, ValueEventArgs<bool> e) {
            var subscriber_strong_ref = subscriber_weak_ref.Target as Control;
            if (subscriber_strong_ref != null) subscriber_strong_ref.Enabled = e.Value;
            else {
                    ((Publisher)sender).EnabledChanged -= handler;
                    handler = null; 
            }
    };

    publisher.EnabledChanged += handler;
}

令我困扰的是该事件被硬编码到该方法中。这意味着对于每个新事件,都有一个新的方法可以编写。

我摆弄并设法想出了这个通用的解决方案:

private static void SetAnyGenericHandler<S, T>(
     Action<EventHandler<T>> add,     //to add event listener to publisher
     Action<EventHandler<T>> remove,  //to remove event listener from publisher
     S subscriber,                    //ref to subscriber (to pass to action)
     Action<S, T> action)             //called when event is raised
    where T : EventArgs
    where S : class
{
    var subscriber_weak_ref = new WeakReference(subscriber);
    EventHandler<T> handler = null;
    handler = delegate(object sender, T e)
    {
        var subscriber_strong_ref = subscriber_weak_ref.Target as S;
        if(subscriber_strong_ref != null)
        {
            Console.WriteLine("New event received by subscriber");
            action(subscriber_strong_ref, e);
        }
        else
        {
            remove(handler);
            handler = null;
        }
    };
    add(handler);
}

然而该解决方案的问题在于它只是通用的,它无法处理标准的 winforms MouseUp、MouseDown 等...

所以我试着让它变得均匀more通用的:

private static void SetAnyHandler<T, R>(
    Action<T> add,      //to add event listener to publisher
    Action<T> remove,   //to remove event listener from publisher
    Subscriber subscriber,  //ref to subscriber (to pass to action)
    Action<Subscriber, R> action) 
    where T : class
{
    var subscriber_weak_ref = new WeakReference(subscriber);
    T handler = null;
    handler = delegate(object sender, R e) //<-compiler doesn't like this line
    {
        var subscriber_strong_ref = subscriber_weak_ref.Target as Subscriber;
        if(subscriber_strong_ref != null)
        {
            action(subscriber_strong_ref, e);
        }
        else
        {
            remove(handler);
            handler = null;
        }
    };
    remove(handler);
}

然而,正如我暗示的那样here,这不会编译,因为没有办法限制 T 成为委托。

那一刻,我几乎放弃了。尝试与 C# 规范作斗争是没有意义的。

然而昨天,我发现了Reactive框架中的Observable.FromEvent方法,我没有实现,但用法似乎有点熟悉,而且很有趣:

var mousedown = Observable.FromEvent<MouseEventHandler, MouseDownEventArgs>(
      h => new MouseEventHandler(h),
      h => control.MouseDown += h,
      h => control.MouseDown -= h);

这是引起我注意的第一个论点。这是缺少委托类型约束的解决方法。我们通过传入创建委托的函数来获取它。

将所有这些放在一起,我们就得到了本答案顶部所示的解决方案。

事后的想法

我强烈建议您花时间了解反应式框架(或者无论它最终被称为什么)。这非常有趣,而且有点令人兴奋。我怀疑它也会使这样的问题变得完全多余。

到目前为止,我见过的最有趣的东西是视频Channel9.

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

与 lambda 一起使用的弱事件处理程序模型 的相关文章

随机推荐

  • Ruby 1.9 Ramaze 应用程序因“非法指令”而失败

    我有一个应用程序 在擦除系统并安装 Snow Leopard 后 我正尝试使其再次运行 我从 Macports 现在是更高版本 安装了 Ruby 1 9 开发服务器启动得很好 但在第一个请求时就死掉了 只告诉我 非法指令 我不知道是什么原因
  • ruby install Rails 报错 ---无法构建 gem 本机扩展。(Windows 平台)

    Ruby 新手 刚刚开始 Rails 之旅 已经搜索过 stackoverflow 但很多这些问题都是很久以前的事了 我的英文不好 请耐心看我的描述 这是我的情况 希望我说清楚 I m on windows 当我在线学习 Ruby 课程时
  • 编辑模式下 DatagridView 中的组合框

    我有一个只读模式设置为 true 不可编辑 的 DataGridView 它在表单加载时从数据库获取值 当我将只读模式设置为 false 可编辑模式 时 我希望特定列 例如部门 显示为组合框 以便我可以从那里选择值 当我进入只读模式时 Co
  • Primefaces ajax根据backbean结果更新不同的面板

    我是 JSF Primefaces 和 Ajax 的新手 所以我想要做的是 如果我的 back bean 上的验证为 true 则更新一个面板 如果为 false 则更新另一个面板
  • 正则表达式混淆 \s 和 " "

    在正则表达式中 我知道何时使用 s 来表示空格 但是 在以下情况下 它们会有所不同 a sb 带有 s a b 空白字段 如果你能向我解释的话 非常感谢 s 字符类匹配所有 空白字符 而不仅仅是空格 这包括制表符 t 如果允许多行匹配 则包
  • Spark Python:如何计算 RDD 中每行之间的 Jaccard 相似度?

    我有一个包含大约 50k 不同行和 2 列的表 你可以认为每一行都是一部电影 列是该电影的属性 ID 该电影的 id Tags 电影的一些内容标签 以每部电影的字符串列表的形式 数据看起来像这样 movie 1 浪漫 喜剧 英语 电影 2
  • Xcode 中的多个目标:“无法启动模拟应用程序:未知错误。”

    我正在尝试在 Xcode 中构建多个目标 以简化创建应用程序的 lite 和 pro 版本的过程 从理论上讲 这很棒 我可以将定义传递给GCC PREPROCESSOR DEFINITIONS在我的代码中使用 但是 由于尝试在模拟器中启动第
  • 使用 VBA 进行条件格式设置

    我想要使 用条件格式的正确代码 我有第 4 季度销售表总和的数据 K8 K207 我想在有 3 个条件的情况下应用条件格式 将大于 1 00 000 的年份的 K 列 年度销售总额 突出显示为绿色 90 000 至 1 00 000 之间为
  • 电子 NODE_MODULE_VERSION 错误和重建的串行端口无法修复

    操作系统 win 10 Node js v12 18 3 电子 v10 1 1 js 程序的打印版本 进程 版本 节点 12 16 3 进程 版本 模块 82 通过以下方式安装串口 npm 安装串口 npm 启动并出现错误 错误 模块 D
  • export::graph2office 移动轴标签

    我在 R RStudio 中绘制了图ggplot2 当我通过导出它们时export graph2office 标签四处移动 但是 只有当我指定标签的字体时才会发生这种情况 library ggplot2 library export plo
  • delphi web脚本的web部分到底是什么?

    我目前开始将 Delphi Web Script 集成到我的应用程序中 基本上仅作为脚本引擎 与函数 类等交互 在我看来 标准 delphi 开源质量的很棒的软件 但只是出于好奇 该项目的 Web 部分 到底是什么 打算如何使用 它在商业上
  • Struts Action 中的多个入口点(迁移 Struts 2.2.3 -> 2.3.1)

    我有一个行动struts xml
  • 使用 Numpy 高效计算欧几里德距离矩阵

    我在二维空间中有一组点 需要计算每个点到其他点的距离 我的点数量相对较少 可能最多 100 个 但是 因为我需要经常快速地执行此操作 以确定这些移动点之间的关系 而且我知道迭代这些点可能会很糟糕由于 O n 2 复杂度 我正在寻找利用 nu
  • 以编程方式禁用鼠标和键盘

    我想以编程方式在 Mac 上暂时禁用鼠标和键盘输入 使用 Objective C C Unix 然后重新启用它们 我制作了一个小型开源应用程序 允许您有选择地禁用键盘CGEventTap来自 OS X 的功能 它位于 Carbon Fram
  • Nhibernate 查询选择按行分组的计数

    我需要使用 NHibernate 获取此查询 Select RequestStatus Status Count ApprovalRequest Id From ApprovalRequest Inner Join RequestStatu
  • 在每行末尾添加文本

    我在 Linux 命令行上并且有文件 127 0 0 1 128 0 0 0 121 121 33 111 I want 127 0 0 1 80 128 0 0 0 80 121 121 33 111 80 我记得我的同事使用 sed 来
  • 如何使用 LWP 获取网页的开头部分?

    有谁知道通过 GET 或 POST 请求仅获取 50 网页的最佳方法吗 我获取的网页需要 10 20 秒才能完全加载 而且我只需要从页面开头过滤几行即可 use 5 010 use strictures use LWP UserAgent
  • Orchard 自定义表单下拉列表

    在尝试了 Orchards 的自定义表单模块之后 我决定使用下拉列表来选择特定的人 并将其电子邮件作为该所选选项的值 当我创建表单时 我无论如何都看不到您可以为您的选项设置值 See below image for example 不认为有
  • iPhone 如何按升序对 NSMutableArray 进行排序?

    我有 SQLite 数据库 DB 并将其作为模型类 并在 NSMutableArray 中获取 DB 的数据 我的数据库与这个数据库类似 学生姓名 注册号 加入日期 DoJ 我成功在 tableView 中显示 DoJ 但我想在 table
  • 与 lambda 一起使用的弱事件处理程序模型

    好的 所以这更像是一个答案而不是一个问题 但在询问之后这个问题 并将来自的各个部分组合在一起达斯汀 坎贝尔 Egor 还有来自 的最后一个提示 IObservable Rx Reactive 框架 我想我已经为这个特定问题找到了一个可行的解