在C#中取消订阅匿名方法

时间:2008-10-08 15:24:46

标签: c# delegates anonymous-methods

是否可以从事件中取消订阅匿名方法?

如果我订阅了这样的活动:

void MyMethod()
{
    Console.WriteLine("I did it!");
}

MyEvent += MyMethod;

我可以这样取消订阅:

MyEvent -= MyMethod;

但如果我使用匿名方法订阅:

MyEvent += delegate(){Console.WriteLine("I did it!");};

是否可以取消订阅此匿名方法?如果是这样,怎么样?

13 个答案:

答案 0 :(得分:213)

Action myDelegate = delegate(){Console.WriteLine("I did it!");};

MyEvent += myDelegate;


// .... later

MyEvent -= myDelegate;

只需保留对代表的引用。

答案 1 :(得分:138)

一种技术是声明一个变量来保存匿名方法,该方法随后可以在匿名方法本身中使用。这对我有用,因为在处理事件后,所需的行为是取消订阅。

示例:

MyEventHandler foo = null;
foo = delegate(object s, MyEventArgs ev)
    {
        Console.WriteLine("I did it!");
        MyEvent -= foo;
    };
MyEvent += foo;

答案 2 :(得分:21)

从内存中,当涉及使用匿名方法创建的委托的等效性时,规范明确地不保证行为。

如果您需要取消订阅,则应使用“正常”方法或将代理保留在其他位置,以便您可以取消订阅与您以前订阅的完全相同的委托。

答案 3 :(得分:17)

在3.0中可以缩短为:

MyHandler myDelegate = ()=>Console.WriteLine("I did it!");
MyEvent += myDelegate;
...
MyEvent -= myDelegate;

答案 4 :(得分:9)

您可以检测您的类,而不是保留对任何委托的引用,以便将事件的调用列表返回给调用者。基本上你可以写这样的东西(假设MyEvent在MyClass中声明):

public class MyClass 
{
  public event EventHandler MyEvent;

  public IEnumerable<EventHandler> GetMyEventHandlers()  
  {  
      return from d in MyEvent.GetInvocationList()  
             select (EventHandler)d;  
  }  
}

因此,您可以从MyClass外部访问整个调用列表,并取消订阅您想要的任何处理程序。例如:

myClass.MyEvent -= myClass.GetMyEventHandlers().Last();

我写了一篇关于此tecnique here的完整帖子。

答案 5 :(得分:7)

C# 7.0 local functions功能发布以来,suggested的方法J c变得非常简洁。

void foo(object s, MyEventArgs ev)
{
    Console.WriteLine("I did it!");
    MyEvent -= foo;
};
MyEvent += foo;

所以,老实说,你没有匿名函数作为变量。但我想在你的案例中使用它的动机可以应用于本地函数。

答案 6 :(得分:6)

一种蹩脚的做法:

public class SomeClass
{
  private readonly IList<Action> _eventList = new List<Action>();

  ...

  public event Action OnDoSomething
  {
    add {
      _eventList.Add(value);
    }
    remove {
      _eventList.Remove(value);
    }
  }
}
  1. 覆盖事件添加/删除方法。
  2. 保留这些事件处理程序的列表。
  3. 如果需要,请清除所有内容并重新添加其他内容。
  4. 这可能不起作用或者是最有效的方法,但应该完成工作。

答案 7 :(得分:2)

如果您希望能够控制取消订阅,那么您需要转到您接受的答案中指明的路线。但是,如果您只关心在订阅类超出范围时清除引用,那么还有另一个(稍微复杂的)解决方案涉及使用弱引用。我刚刚在这个主题上发布了question and answer

答案 8 :(得分:2)

一个简单的解决方案:

只需将eventhandle变量作为参数传递给自身。 如果您遇到因多线程而无法访问原始创建变量的情况,则可以使用此事件:

MyEventHandler foo = null;
foo = (s, ev, mehi) => MyMethod(s, ev, foo);
MyEvent += foo;

void MyMethod(object s, MyEventArgs ev, MyEventHandler myEventHandlerInstance)
{
    MyEvent -= myEventHandlerInstance;
    Console.WriteLine("I did it!");
}

答案 9 :(得分:0)

如果你想用这个委托引用一些对象,你可以使用Delegate.CreateDelegate(Type,Object target,MethodInfo methodInfo) .net认为委托等于target和methodInfo

答案 10 :(得分:0)

如果最好的方法是在订阅的eventHandler上保留一个引用,可以使用Dictionary来实现。

在这个例子中,我必须使用匿名方法为一组DataGridViews包含mergeColumn参数。

使用带有enable参数设置为true的MergeColumn方法可以在使用false时使用该事件禁用它。

static Dictionary<DataGridView, PaintEventHandler> subscriptions = new Dictionary<DataGridView, PaintEventHandler>();

public static void MergeColumns(this DataGridView dg, bool enable, params ColumnGroup[] mergedColumns) {

    if(enable) {
        subscriptions[dg] = (s, e) => Dg_Paint(s, e, mergedColumns);
        dg.Paint += subscriptions[dg];
    }
    else {
        if(subscriptions.ContainsKey(dg)) {
            dg.Paint -= subscriptions[dg];
            subscriptions.Remove(dg);
        }
    }
}

答案 11 :(得分:0)

有一种方法可以通过自己实现闭包而不是 lambda 表达式来解决这个问题。

假设用作捕获变量的类如下。

public class A
{
    public void DoSomething()
    {
        ...
    }
}

public class B
{
    public void DoSomething()
    {
        ...
    }
}

public class C
{
    public void DoSomething()
    {
        ...
    }
}

这些类将用作捕获变量,因此我们将它们实例化。

A a = new A();
B b = new B();
C c = new C();

实现闭包类,如下所示。

private class EventHandlerClosure
{
    public A a;
    public B b;
    public C c;

    public event EventHandler Finished;

    public void MyMethod(object, MyEventArgs args)
    {
        a.DoSomething();
        b.DoSomething();
        c.DoSomething();
        Console.WriteLine("I did it!");

        Finished?.Invoke(this, EventArgs.Empty);
    }
}

实例化闭包类,创建处理程序,然后订阅事件并订阅从闭包类的 Finished 事件取消订阅的 lambda 表达式。

var closure = new EventHandlerClosure
{
    a = a,
    b = b,
    c = c
};
var handler = new MyEventHandler(closure.MyMethod);
MyEvent += handler;
closure.Finished += (s, e)
{
    MyEvent -= handler;
}

答案 12 :(得分:0)

我最近为一个 C# 项目发现了这个相当古老的线程,并发现所有答案都非常有用。但是,对于我的特定用例,有一个方面效果不佳 - 它们都将取消订阅事件的负担推给了订阅者。我知道有人可能会说这是订阅者的工作来处理这个问题,但这对我的项目来说并不现实。

我的事件的主要用例是监听定时器以对动画进行排序(这是一个游戏)。在这种情况下,我使用了很多匿名委托来将序列链接在一​​起。存储对这些的引用不太实用。

为了解决这个问题,我围绕一个事件创建了一个包装类,让您可以订阅单个调用。

internal class EventWrapper<TEventArgs> {
    
    private event EventHandler<TEventArgs> Event;
    private readonly HashSet<EventHandler<TEventArgs>> _subscribeOnces;
    
    internal EventWrapper() {
        _subscribeOnces = new HashSet<EventHandler<TEventArgs>>();
    }

    internal void Subscribe(EventHandler<TEventArgs> eventHandler) {
        Event += eventHandler;
    }

    internal void SubscribeOnce(EventHandler<TEventArgs> eventHandler) {
        _subscribeOnces.Add(eventHandler);
        Event += eventHandler;
    }

    internal void Unsubscribe(EventHandler<TEventArgs> eventHandler) {
        Event -= eventHandler;
    }

    internal void UnsubscribeAll() {
        foreach (EventHandler<TEventArgs> eventHandler in Event?.GetInvocationList()) {
            Event -= eventHandler;
        }
    }

    internal void Invoke(Object sender, TEventArgs e) {
        Event?.Invoke(sender, e);
        if(_subscribeOnces.Count > 0) {
            foreach (EventHandler<TEventArgs> eventHandler in _subscribeOnces) {
                Event -= eventHandler;
            }
            _subscribeOnces.Clear();
        }
    }

    internal void Remove() {
        UnsubscribeAll();
        _subscribeOnces.Clear();
    }
}

在类中使用它的附带好处是您可以将其设为私有并仅公开您想要的功能。例如,只公开 SubscribeOnce(而不是 Subscribe)方法。

public class MyClass {
    
    private EventWrapper<MyEventEventArgs> myEvent = new EventWrapper<MyEventEventArgs>();
    
    public void FireMyEvent() {
        myEvent.Invoke(this, new MyEventEventArgs(1000, DateTime.Now));
    }
    
    public void SubscribeOnce(EventHandler<MyEventEventArgs> eventHandler) {
        myEvent.SubscribeOnce(eventHandler);
    }
    
    public class MyEventEventArgs : EventArgs {
        public int MyInt;
        public DateTime MyDateTime;
        
        public MyEventEventArgs(int myInt, DateTime myDateTime) {
            MyInt = myInt;
            MyDateTime = myDateTime;
        }
    }
}

这里的权衡是为每个事件拥有一个 this 实例的开销更多,但是在我的场景中 - 这是一个可以接受的权衡,以确保垃圾被有效收集并且代码在订阅者端更易于维护。 Full example here