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

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

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

MyEvent += MyMethod;

我可以像这样取消订阅:

MyEvent -= MyMethod;

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

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

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


当前回答

有一种方法可以通过自己实现闭包而不是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;
}

其他回答

一种技术是声明一个变量来保存匿名方法,该变量将在匿名方法本身内部可用。这对我来说很有效,因为我想要的行为是在事件处理后取消订阅。

例子:

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

有一种方法可以通过自己实现闭包而不是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;
}

有点蹩脚的方法:

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

  ...

  public event Action OnDoSomething
  {
    add {
      _eventList.Add(value);
    }
    remove {
      _eventList.Remove(value);
    }
  }
}

重写事件添加/删除方法。 保留这些事件处理程序的列表。 当需要时,清除它们并重新添加其他的。

这可能行不通,也不是最有效的方法,但应该能把工作完成。

自从c# 7.0局部函数特性发布以来,J C建议的方法变得非常简洁。

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

老实说,这里没有一个匿名函数作为变量。但我想在你的例子中使用它的动机可以应用到局部函数中。

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

MyEvent += myDelegate;


// .... later

MyEvent -= myDelegate;

只要保持对委托的引用。