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


222

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

如果我订阅这样的活动:

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

MyEvent += MyMethod;

我可以这样退订:

MyEvent -= MyMethod;

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

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

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


4
至于为什么你不能这样做:stackoverflow.com/a/25564492/23354
Marc Gravell

Answers:


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

MyEvent += myDelegate;


// .... later

MyEvent -= myDelegate;

只需保持对代理人的引用即可。


141

一种技术是声明一个变量来保存匿名方法,然后该变量将在匿名方法本身内部可用。这对我有用,因为所需的行为是在处理事件后退订。

例:

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

1
使用这种代码,Resharper抱怨访问修改过的闭包...这种方法可靠吗?我的意思是,我们确定匿名方法主体内的'foo'变量确实引用了匿名方法本身吗?
BladeWise

7
我找到了答案的答案,那就是“ foo”将真正拥有对匿名方法itslef的引用。捕获的变量被修改,因为它是在将匿名方法分配给它之前捕获的。
BladeWise

2
那正是我所需要的!我错过了= null。(MyEventHandler foo =委托{... MyEvent- = foo;}; MyEvent + = foo;无效...)
TDaver

如果您将Resharper 6.1声明为数组,则不会抱怨。似乎有些不可思议,但是我将一味地信任我的工具:MyEventHandler [] foo = {null}; foo [0] = ... {... MyEvent-= foo [0]; }; MyEvent + = foo [0];
Mike Post

21

从内存来看,当使用匿名方法创建的委托等效时,规范显然不保证任何一种行为。

如果您需要退订,则应该使用“常规”方法或将委托保留在其他位置,以便可以使用与您订阅时完全相同的委托退订。


我乔恩,你是什么意思?我不明白 “ J c”公开的解决方案将无法正常工作?
埃里克·厄勒

@EricOuellet:答案基本上是“将委托保留在其他地方,以便您可以使用与订阅时完全相同的委托取消订阅”的实现。
乔恩·斯基特

乔恩,很抱歉,我多次阅读您的回答,试图弄清您的意思以及“ J c”解决方案未使用同一委托进行订阅和退订的地方,但是我无法找到它。也许您可以在一篇文章中指出我的意思?我了解您的声誉,我真的很想了解您的意思,您可以链接的任何内容都会非常感谢。
埃里克·厄勒

1
我发现:msdn.microsoft.com/en-us/library/ms366768.aspx,但他们建议不要使用匿名,但他们不说存在任何重大问题?
埃里克·厄勒

我发现它...感谢了很多(见迈克尔BLOME答案):social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/...
埃里克·韦莱

16

在3.0中可以简化为:

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

15

由于C#7.0的本地功能功能已经发布,该办法建议通过Ĵç变得很整洁。

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

因此,说实话,您这里没有匿名函数作为变量。但是我想在您的情况下使用它的动机可以应用于局部函数。


1
为了使可读性更好,可以移动MyEvent + = foo;。在foo声明之前的行。
Mark Zhukovsky

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 这里


2
如果他们订阅了我的活动,这是否意味着我可能会意外取消订阅该活动的其他实例(即不是我)?
dumbledad 2014年

@dumbledad当然会取消注册最后一个注册的。如果要动态取消订阅特定的匿名委托,则需要以某种方式进行识别。我建议然后保留一个参考文献:)
LuckyLikey

这很酷,您正在做什么,但是我无法想象有一种情况会有用。但是我并没有真正解决OP的问题。-> +1。恕我直言,如果以后要取消注册,则不应使用匿名委托。保持它们很愚蠢->更好地使用Method。在调用列表中仅删除一些委托是非常随机且无用的。如我错了请纠正我。:)
LuckyLikey

6

me脚的方法:

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. 需要时,清除所有内容并重新添加其他内容。

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


14
如果您认为自己很la脚,请不要发布。
杰里·尼克松

2

如果您希望能够控制退订,那么您需要按照接受的答案中指示的路线进行操作。但是,如果您只担心在订阅类超出范围时清理引用,那么还有另一种(略为复杂的)解决方案,其中涉及使用弱引用。我刚刚发布了关于该主题的问答


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!");
}

如果MyEvent在MyEvent -= myEventHandlerInstance;运行之前被两次调用怎么办?如果可能,您会遇到错误。但是我不确定是否是这种情况。
LuckyLikey

0

如果要使用此委托引用某个对象,则可以使用Delegate.CreateDelegate(Type,Object target,MethodInfo methodInfo).net将该委托视为等于target和methodInfo


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