146

学习以下 C# 类:

c1 {
 event EventHandler someEvent;
}

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

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

4

10 回答 10

186

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

在课堂之外,你不能这样做——事件基本上暴露了“订阅”和“取消订阅”,仅此而已。

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

有关更多信息,请参阅我关于事件和代表的文章。

于 2008-09-30T16:05:02.233 回答
34

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

public class c1
{
    event EventHandler someEvent;
    public ResetSubscriptions() => someEvent = null;    
}
于 2008-09-30T15:33:46.550 回答
8
class c1
{
    event EventHandler someEvent;
    ResetSubscriptions() => someEvent = delegate { };
}

delegate { }使用它比null避免 null ref 异常更好。

于 2014-04-30T16:33:55.273 回答
6

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

于 2008-10-01T11:17:04.630 回答
6

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

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

这里有人提出使用Delegate.RemoveAll方法。如果您使用它,示例代码可以遵循以下形式。但这真的很愚蠢。为什么不只是SomeEvent=nullClearSubscribers()函数内部?

public void ClearSubscribers ()
{
   SomeEvent = (EventHandler) Delegate.RemoveAll(SomeEvent, SomeEvent);
   // Then you will find SomeEvent is set to null.
}
于 2012-10-16T12:00:05.027 回答
5

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

于 2008-09-30T15:36:21.320 回答
3

概念性扩展无聊评论。

我宁愿使用“事件处理程序”这个词而不是“事件”或“委托”。并将“事件”一词用于其他内容。在某些编程语言(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;

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

摘要:“事件”是“问题”,“事件处理程序(s)”是答案(s)。

于 2011-03-24T18:39:12.553 回答
0

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

Delegate[] dary = TermCheckScore.GetInvocationList();

if ( dary != null )
{
    foreach ( Delegate del in dary )
    {
        TermCheckScore -= ( Action ) del;
    }
}
于 2013-12-20T20:00:54.253 回答
0

这是我的解决方案:

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()){/*...*/}模式来取消订阅调用列表的所有成员。

于 2018-03-05T07:14:39.347 回答
-1

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

// 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();
    }
}
于 2020-01-29T15:37:35.643 回答