【问题标题】:How can I clear event subscriptions in C#?如何在 C# 中清除事件订阅?
【发布时间】:2010-09-14 07:00:30
【问题描述】:

参加以下 C# 课程:

c1 {
 event EventHandler someEvent;
}

如果有很多订阅c1someEvent 事件并且我想将它们全部清除,那么实现此目的的最佳方法是什么? 还要考虑到此事件的订阅可能/是 lambdas/匿名委托。

目前我的解决方案是向c1 添加一个ResetSubscriptions() 方法,将someEvent 设置为null。我不知道这是否有任何看不见的后果。

【问题讨论】:

标签: c# .net events delegates


【解决方案1】:

在类中,您可以将(隐藏的)变量设置为 null。空引用是有效地表示空调用列表的规范方式。

在课堂之外,您不能这样做 - 事件基本上会公开“订阅”和“取消订阅”,仅此而已。

值得注意的是,类似字段的事件实际上在做什么——它们同时创建了一个变量一个事件。在类中,您最终会引用该变量。从外部,您引用该事件。

请查看我的article on events and delegates 了解更多信息。

【讨论】:

  • 如果你很固执,可以通过反射强行清除。见stackoverflow.com/questions/91778/…
  • @Brian:这取决于实现。如果它只是类似字段的事件或EventHandlerList,您也许可以。不过,您必须认识到这两种情况 - 并且可能有任意数量的其他实现。
  • @Joshua:不,它会将变量设置为空值。我同意该变量不会被称为hidden
  • @JonSkeet 这就是我(以为)我说的。它的写法让我困惑了 5 分钟。
  • @JoshuaLamusga:嗯,你说它会清除调用列表,这听起来像是在修改现有对象。
【解决方案2】:

向 c1 添加一个方法,将 'someEvent' 设置为 null。

public class c1
{
    event EventHandler someEvent;
    public ResetSubscriptions() => someEvent = null;    
}

【讨论】:

  • 这就是我看到的行为。正如我在问题中所说,我不知道我是否忽略了某些东西。
【解决方案3】:
class c1
{
    event EventHandler someEvent;
    ResetSubscriptions() => someEvent = delegate { };
}

最好使用delegate { }而不是null来避免null ref异常。

【讨论】:

  • 为什么?你能扩展一下这个答案吗?
  • @S.Buda 因为如果它为空,那么您将获得空参考。这就像使用 List.Clear()myList = null
【解决方案4】:

在类中将事件设置为 null 有效。当你处置一个类时,你应该始终将事件设置为 null,GC 会遇到事件问题,并且如果处置的类有悬空事件,则可能不会清理它。

【讨论】:

    【解决方案5】:

    清除所有订阅者的最佳做法是,如果您想将此功能公开给外部,则通过添加另一个公共方法将 someEvent 设置为 null。这没有看不见的后果。前提是要记得用关键字'event'声明SomeEvent。

    请参阅本书 - C# 4.0 简而言之,第 125 页。

    这里有人建议使用Delegate.RemoveAll 方法。如果您使用它,示例代码可以遵循以下形式。但这真的很愚蠢。为什么不在ClearSubscribers() 函数中只使用SomeEvent=null

    public void ClearSubscribers ()
    {
       SomeEvent = (EventHandler) Delegate.RemoveAll(SomeEvent, SomeEvent);
       // Then you will find SomeEvent is set to null.
    }
    

    【讨论】:

    • Delegate.RemoveAll 对 MulticastDelegate 有效:public delegate string TableNameMapperDelegate(Type type);public static TableNameMapperDelegate TableNameMapper; ?
    【解决方案6】:

    您可以使用 Delegate.Remove 或 Delegate.RemoveAll 方法实现此目的。

    【讨论】:

    • 我认为这不适用于 lambda 表达式或匿名委托。
    【解决方案7】:

    概念性扩展无聊评论。

    我宁愿使用“事件处理程序”这个词而不是“事件”或“委托”。并将“事件”一词用于其他内容。在某些编程语言(VB.NET、Object Pascal、Objective-C)中,“事件”被称为“消息”或“信号”,甚至还有“消息”关键字,以及特定的糖语法。

    const
      WM_Paint = 998;  // <-- "question" can be done by several talkers
      WM_Clear = 546;
    
    type
      MyWindowClass = class(Window)
        procedure NotEventHandlerMethod_1;
        procedure NotEventHandlerMethod_17;
    
        procedure DoPaintEventHandler; message WM_Paint; // <-- "answer" by this listener
        procedure DoClearEventHandler; message WM_Clear;
      end;
    

    并且,为了响应该“消息”,“事件处理程序”响应,无论是单个委托还是多个委托。

    总结: “事件”是“问题”,“事件处理程序”是答案。

    【讨论】:

      【解决方案8】:

      删除所有事件,假设事件是“Action”类型:

      Delegate[] dary = TermCheckScore.GetInvocationList();
      
      if ( dary != null )
      {
          foreach ( Delegate del in dary )
          {
              TermCheckScore -= ( Action ) del;
          }
      }
      

      【讨论】:

      • 如果你在声明事件的类型内,你不需要这样做,你可以将它设置为 null,如果你在类型之外,那么你不能获取委托的调用列表。此外,如果事件为空,您的代码会在调用 GetInvocationList 时引发异常。
      【解决方案9】:

      这是我的解决方案:

      public class Foo : IDisposable
      {
          private event EventHandler _statusChanged;
          public event EventHandler StatusChanged
          {
              add
              {
                  _statusChanged += value;
              }
              remove
              {
                  _statusChanged -= value;
              }
          }
      
          public void Dispose()
          {
              _statusChanged = null;
          }
      }
      

      您需要调用Dispose() 或使用using(new Foo()){/*...*/} 模式取消订阅调用列表的所有成员。

      【讨论】:

        【解决方案10】:

        而不是手动添加和删除回调并在各处声明一堆委托类型:

        // The hard way
        public delegate void ObjectCallback(ObjectType broadcaster);
        
        public class Object
        {
            public event ObjectCallback m_ObjectCallback;
            
            void SetupListener()
            {
                ObjectCallback callback = null;
                callback = (ObjectType broadcaster) =>
                {
                    // one time logic here
                    broadcaster.m_ObjectCallback -= callback;
                };
                m_ObjectCallback += callback;
        
            }
            
            void BroadcastEvent()
            {
                m_ObjectCallback?.Invoke(this);
            }
        }
        

        您可以尝试这种通用方法:

        public class Object
        {
            public Broadcast<Object> m_EventToBroadcast = new Broadcast<Object>();
        
            void SetupListener()
            {
                m_EventToBroadcast.SubscribeOnce((ObjectType broadcaster) => {
                    // one time logic here
                });
            }
        
            ~Object()
            {
                m_EventToBroadcast.Dispose();
                m_EventToBroadcast = null;
            }
        
            void BroadcastEvent()
            {
                m_EventToBroadcast.Broadcast(this);
            }
        }
        
        
        public delegate void ObjectDelegate<T>(T broadcaster);
        public class Broadcast<T> : IDisposable
        {
            private event ObjectDelegate<T> m_Event;
            private List<ObjectDelegate<T>> m_SingleSubscribers = new List<ObjectDelegate<T>>();
        
            ~Broadcast()
            {
                Dispose();
            }
        
            public void Dispose()
            {
                Clear();
                System.GC.SuppressFinalize(this);
            }
        
            public void Clear()
            {
                m_SingleSubscribers.Clear();
                m_Event = delegate { };
            }
        
            // add a one shot to this delegate that is removed after first broadcast
            public void SubscribeOnce(ObjectDelegate<T> del)
            {
                m_Event += del;
                m_SingleSubscribers.Add(del);
            }
        
            // add a recurring delegate that gets called each time
            public void Subscribe(ObjectDelegate<T> del)
            {
                m_Event += del;
            }
        
            public void Unsubscribe(ObjectDelegate<T> del)
            {
                m_Event -= del;
            }
        
            public void Broadcast(T broadcaster)
            {
                m_Event?.Invoke(broadcaster);
                for (int i = 0; i < m_SingleSubscribers.Count; ++i)
                {
                    Unsubscribe(m_SingleSubscribers[i]);
                }
                m_SingleSubscribers.Clear();
            }
        }
        

        【讨论】:

        • 您能否格式化您的问题并删除左侧的所有空白?当您从 IDE 复制和粘贴时,可能会发生这种情况
        • 刚刚去掉那个空白,我的坏
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-02-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-10-27
        相关资源
        最近更新 更多