是否可以取消订阅某个事件的匿名方法?

如果我订阅这样的事件:

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

MyEvent += MyMethod;


我可以取消订阅像这样订阅:

MyEvent -= MyMethod;


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

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


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

评论

至于为什么您不能这样做:stackoverflow.com/a/25564492/23354

#1 楼

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

MyEvent += myDelegate;


// .... later

MyEvent -= myDelegate;


请保持对委托人的引用。

#2 楼

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

示例:

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


评论


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

–BladeWise
10年7月28日在15:13

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

–BladeWise
2010年7月29日在7:23

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

–TDaver
2011-2-21在10:33

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

– Mike Post
2012-3-14的3:06



#3 楼

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

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

评论


我乔恩,你是什么意思?我不明白“ J c”公开的解决方案将不能正常工作?

– Eric Ouellet
2012年10月17日下午13:33

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

–乔恩·斯基特(Jon Skeet)
2012年10月17日下午13:38

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

– Eric Ouellet
2012年10月18日在12:59

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

– Eric Ouellet
2012-10-18 13:02



我找到了...非常感谢(请参阅Michael Blome答案):social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread / ...

– Eric Ouellet
2012-10-18 13:11



#4 楼

由于发布了C#7.0本地函数功能,J c建议的方法变得非常简洁。

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


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

评论


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

–马克·朱可夫斯基
19-09-24在14:56

#5 楼

在3.0中可以简化为:

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


#6 楼

无需保留对任何委托的引用,您可以对类进行检测,以便将事件的调用列表返回给调用方。基本上,您可以编写如下代码(假设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();


我在这里写了一篇有关该技术的完整文章。

评论


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

– dumbledad
2014年3月25日22:53

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

– LuckyLikey
16年2月2日在13:24

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

– LuckyLikey
16年6月2日在13:35

#7 楼

一种la脚的方法:

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

  ...

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



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

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

评论


如果您认为它很la脚,请不要发布。

–杰里·尼克松(Jerry Nixon)
2011-3-4 15:48

#8 楼

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

#9 楼

一个简单的解决方案:

只需将eventhandle变量作为参数传递给它自己。
事件如果您由于多线程而无法访问原始创建的变量,则可以使用以下方法: br />
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-= myEventHandlerInstance之前两次调用MyEvent怎么办?跑了吗如果可能,您会遇到错误。但是我不确定是否是这种情况。

– LuckyLikey
16年2月2日在13:27

#10 楼

如果要使用此委托引用某个对象,则可以使用Delegate.CreateDelegate(Type,Object target,MethodInfo methodInfo)
.net考虑委托与target和methodInfo相等

#11 楼

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

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

在将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);
        }
    }
}