如果我订阅这样的事件:
void MyMethod()
{
Console.WriteLine("I did it!");
}
MyEvent += MyMethod;
我可以取消订阅像这样订阅:
MyEvent -= MyMethod;
但是如果我使用匿名方法订阅:
MyEvent += delegate(){Console.WriteLine("I did it!");};
是否可能取消订阅此匿名方法?如果可以,怎么办?
#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);
}
}
}
评论
至于为什么您不能这样做:stackoverflow.com/a/25564492/23354