【问题标题】:How to determine if an event is already subscribed如何确定一个事件是否已经被订阅
【发布时间】:2010-04-23 08:45:52
【问题描述】:

在我的 .NET 应用程序中,我订阅了另一个类的事件。订阅是有条件的。我在控件可见时订阅事件,并在它变得不可见时取消订阅它。但是,在某些情况下,即使控件不可见,我也不想取消订阅事件,因为我想要后台线程上发生的操作的结果。

有没有一种方法可以确定某个类是否已经订阅了该事件?

我知道我们可以在通过检查null 的事件来引发该事件的类中执行此操作,但是如何在将订阅该事件的类中执行此操作?

【问题讨论】:

标签: c# .net


【解决方案1】:

event 关键字是明确发明的,以防止您做自己想做的事。它限制了对底层delegate 对象的访问,因此没有人可以直接弄乱它存储的事件处理程序订阅。事件是委托的访问器,就像属性是字段的访问器一样。属性只允许获取和设置,事件只允许添加和删除。

这可以保证您的代码安全,其他代码只有在知道事件处理程序方法和目标对象的情况下才能删除事件处理程序。 C# 语言不允许您命名目标对象,从而提供了额外的安全层。

WinForms 提供了额外的安全层,因此即使您使用反射也变得困难。它将delegate 实例存储在EventHandlerList 中,并以秘密“cookie”作为密钥,您必须知道cookie 才能从列表中挖掘对象。

好吧,不要去那里。用一点代码解决你的问题是微不足道的:

private bool mSubscribed;

private void Subscribe(bool enabled)
{
    if (!enabled) textBox1.VisibleChanged -= textBox1_VisibleChanged;
    else if (!mSubscribed) textBox1.VisibleChanged += textBox1_VisibleChanged;

    mSubscribed = enabled;
}

【讨论】:

  • 您是否碰巧有任何博客、第 9 频道视频或 msdn 文章讨论使 事件 难以交互背后的设计方法?也许如果我了解为什么,以及打算机制来完成一些(通常)微不足道的事情,我可能会更容易为自己的问题想出微不足道的解决方案套。
  • 您的活动并不难进行互动。弄乱别人的事件变得很困难,那就是弄乱私人部分。提出一个问题。
  • 好吧,为什么搞乱别人的活动很难?
  • 如果这里有多个订阅者怎么办?我不想为同一个订阅者注册两次...我不想为任何单个订阅者触发两次事件。当订阅者是远程实体,通过 wcf 通道时,这个问题也很重要。
  • 哦,让我知道当这个用户谈论控件时,他的意思是[winforms]。从他的个人资料中不难发现。
【解决方案2】:

假设您无权访问声明事件的类的内部信息,则无法直接执行此操作。事件仅公开运算符+=-=,仅此而已。您需要在您的订阅类中使用标志或其他机制来了解您是否已经订阅。

【讨论】:

    【解决方案3】:
      /// <summary>
      /// Determine if a control has the event visible subscribed to
      /// </summary>
      /// <param name="controlObject">The control to look for the VisibleChanged event</param>
      /// <returns>True if the control is subscribed to a VisibleChanged event, False otherwise</returns>
      private bool IsSubscribed(Control controlObject)
      {
         FieldInfo event_visible_field_info = typeof(Control).GetField("EventVisible",
            BindingFlags.Static | BindingFlags.NonPublic);
         object object_value = event_visible_field_info.GetValue(controlObject);
         PropertyInfo events_property_info = controlObject.GetType().GetProperty("Events",
            BindingFlags.NonPublic | BindingFlags.Instance);
         EventHandlerList event_list = (EventHandlerList)events_property_info.GetValue(controlObject, null);
         return (event_list[object_value] != null);
      }
    

    【讨论】:

    • 什么是“控制”对象?我必须为这个“控制”脚本使用什么?
    【解决方案4】:

    只要触发事件处理程序,只需检查控件是否可见。

    【讨论】:

    • 我不想这样做,因为事件是定期触发的,我只想在我的控件不可见时使用它们。如果我按照你说的做,那将是一个性能打击。
    • @Ram:为什么你认为它会影响性能?您是否衡量过性能的变化?
    • @Phil:嗨,Phil,这是一个性能打击,因为我正在使用多种表单和多个事件进行此操作。每个表格处理数据差异的方式。因此,为了避免处理数据,我订阅的事件只是表单是可见的。我相信使用布尔值会是一个不错的选择。
    【解决方案5】:

    您能否将决策逻辑放入触发事件的方法中?假设您使用的是 Winforms,它看起来像这样:

     if (MyEvent != null && isCriteriaFulfilled)
    {
        MyEvent();
    }
    

    isCriteriaFulfilled 的位置由您的可见/不可见逻辑决定。

    // 更新 /////

    在您的第一条评论之后,根据this.Visible 的值更改事件处理程序内部的行为是否没有意义?

     a.Delegate += new Delegate(method1);
    ...
    private void method1()
    {
        if (this.Visible)
            // Do Stuff
    }
    

    或者如果你真的需要订阅和取消订阅:

     private Delegate _method1 = null;
    ...
    if(this.visible) 
    {
        if (_method1 == null)
            _method1 = new Delegate(method1);
        a.Delegate += _method1;
    }
    else if (_method1 != null)
    {
        a.Delegate -= _method1;
    } 
    

    【讨论】:

    • 不知道areCriteriaFulfilled在语法上是否更好?
    • 我做如下 if(this.visible) { a.Delegate += new Delegate(method1); } else { a.Delegate -= new Delegate(method1); }
    • 我不想按照你的建议去做,因为事件是定期触发的,我只想在我的控件不可见时使用它们。如果我按照你说的做,那将是一个性能打击。
    • @Ram:再次更新。但我仍然很好奇为什么你认为它会影响性能?订阅事件也会产生分配和(最终)释放内存的开销。
    • @Phil :是的,我同意这会增加开销。谢谢。 :)
    【解决方案6】:

    你不记得你是否已经订阅了吗?到目前为止,这种方法对我来说效果很好。即使您有很多事件或对象,您仍可能只想记住这些(例如,在字典中)。

    另一方面,可见性变化至少对我来说不是订阅/取消订阅的好点。我通常更愿意使用构造/处置,这比每次可见性变化时都更清晰。

    【讨论】:

      【解决方案7】:

      我只是在扩展 Hans 的答案。我只是想确保我不会多次安装我的处理程序,并且在我仍然需要它时不会删除它。这并不能防止恶意或恶意调用者反复取消订阅,因为您需要跟踪调用者,而这只会让您面临重复订阅超出跟踪机制的风险。

      // Tracks how many times the ReflectionOnlyResolveHandler has been requested.
      private static int  _subscribers = 0;
      
      /// <summary>
      /// Register or unregister the ReflectionOnlyResolveHandler.
      /// </summary>
      /// <param name="enable"></param>
      public static void SubscribeReflectionOnlyResolve(bool enable)
      {
          lock(_lock)
          {
              if (_subscribers > 0 && !enable) _subscribers -= 1;
              else if (enable) _subscribers += 1;
      
              if (enable && _subscribers == 1) 
                  AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += ReflectionHelper.ReflectionOnlyResolveHandler;
              else if (_subscribers == 0) 
                  AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= ReflectionHelper.ReflectionOnlyResolveHandler;
          }
      }
      

      【讨论】:

      • 上面的调用者并不是真正的“订阅者”,他们只是要求帮助类注册它的处理程序。
      猜你喜欢
      • 2011-04-05
      • 2010-09-26
      • 2012-01-25
      • 1970-01-01
      • 1970-01-01
      • 2017-06-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多