如何清除C#中的事件订阅?


141

采取以下C#类:

c1 {
 event EventHandler someEvent;
}

如果有很多订阅c1someEvent活动,而我想全部清除,那么实现此目的的最佳方法是什么?还请注意,此事件的订阅可能是lambdas /匿名代表。

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

Answers:


181

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

从类之外,您不能执行此操作-事件基本上公开了“订阅”和“取消订阅”,仅此而已。

值得注意的是,类似字段的事件实际上在做什么-它们正在同时创建变量事件。在该类中,您最终将引用该变量。您可以从外部引用事件。

有关更多信息,请参见我有关事件和委托的文章


3
如果您很固执,可以通过反射将其清除。参见stackoverflow.com/questions/91778/…
Brian

1
@Brian:这取决于实现。如果这只是一个类似字段的事件或EventHandlerList,您也许可以。但是,您必须认识到这两种情况-可能有许多其他实现。
乔恩·斯基特

@Joshua:不,它将变量设置为空值。我同意不会调用该变量hidden
乔恩·斯基特

@JonSkeet我就是这么想的。它的书写方式让我困惑了5分钟。

@JoshuaLamusga:嗯,您说过将清除一个调用列表,这听起来像在修改现有对象。
乔恩·斯基特

34

向c1添加一个将“ someEvent”设置为null的方法。

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

这就是我所看到的行为。就像我在问题中说的那样,我不知道自己是否在俯视。
程序员

8
class c1
{
    event EventHandler someEvent;
    ResetSubscriptions() => someEvent = delegate { };
}

这是更好地使用delegate { }null以避免空REF例外。


2
为什么?您能否扩大这个答案?
S. Buda '18

1
@ S.Buda因为如果它为null,那么您将获得一个null引用。就像使用List.Clear()vs一样myList = null
AustinWBryan

6

在类内部将事件设置为null即可。处置类时,应始终将事件设置为null,GC会遇到事件问题,并且如果存在悬挂事件,则可能无法清理处置的类。


6

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

请参阅第125页上的《 C#4.0》一书。

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

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

5

您可以使用Delegate.Remove或Delegate.RemoveAll方法来实现。


6
我认为这不适用于lambda表达式或匿名委托。
程序员

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;

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

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


1

这是我的解决方案:

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


0

删除所有事件,假定该事件是“动作”类型:

Delegate[] dary = TermCheckScore.GetInvocationList();

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

1
如果您位于声明事件的类型之内,则无需执行此操作,只需将其设置为null,如果您不在该类型之内,则无法获取该委托的调用列表。另外,调用时,如果事件为null,则代码将引发异常GetInvocationList
2013年

-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();
    }
}

您能否格式化问题并删除左侧的所有空白?当您从IDE复制和粘贴时,可能会发生
AustinWBryan

刚刚摆脱了
那片
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.