【问题标题】:Unsubscribe anonymous method in C#在 C# 中取消订阅匿名方法
【发布时间】:2010-09-16 01:13:00
【问题描述】:

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

如果我订阅这样的活动:

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

MyEvent += MyMethod;

我可以这样取消订阅:

MyEvent -= MyMethod;

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

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

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

【问题讨论】:

标签: c# delegates anonymous-methods


【解决方案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”变量真的引用了匿名方法本身吗?
    • 我找到了我的问题的答案,它是 'foo' 将真正包含对匿名方法本身的引用。捕获的变量被修改,因为它是在匿名方法被分配给它之前被捕获的。
    • 这正是我所需要的!我错过了=null。 (MyEventHandler foo = delegate {... MyEvent-=foo;}; MyEvent+=foo; 没用...)
    • Resharper 6.1 如果您将其声明为数组,则不会抱怨。看起来有点奇怪,但我会盲目相信我的工具: MyEventHandler[] foo = { null }; foo[0] = ... { ... MyEvent -= foo[0]; };我的事件 += foo[0];
    【解决方案3】:

    根据记忆,规范明确不保证使用匿名方法创建的委托的等效性。

    如果您需要取消订阅,您应该使用“正常”方法或将委托保留在其他地方,以便您可以使用与您订阅时完全相同的委托来取消订阅。

    【讨论】:

    • 我乔恩,你是什么意思?我不明白。 “J c”暴露的解决方案不会正常工作吗?
    • @EricOuellet:这个答案基本上是“将委托保留在其他地方,这样您就可以使用与您订阅的完全相同的委托取消订阅”的实现。
    • 乔恩,对不起,我多次阅读您的回答,试图弄清楚您的意思以及“J c”解决方案在哪里不使用同一个委托来订阅和取消订阅,但我可以找不到它。也许你可以在一篇文章中指出我的意思,解释你在说什么?我知道你的声誉,我真的很想理解你的意思,任何你能链接到的东西都会非常感激。
    • 我找到了:msdn.microsoft.com/en-us/library/ms366768.aspx 但他们不建议使用匿名但他们没有说有什么大问题?
    • 我找到了...非常感谢(请参阅 Michael Blome 的回答):social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/…
    【解决方案4】:

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

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

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

    【讨论】:

    • 为了提高可读性,您可以移动 MyEvent += foo; foo 声明之前的行。
    【解决方案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();
      

      我已经写了一篇关于这个技术的完整帖子here。

      【讨论】:

      • 这是否意味着如果他们在我之后订阅,我可能会意外取消订阅其他实例(即不是我)?
      • @dumbledad 当然,这将永远取消注册最后一个注册的。如果您想动态取消订阅特定的匿名委托,您需要以某种方式识别它。我建议保留参考:)
      • 它很酷,你在做什么,但我无法想象这可能有用的一种情况。但我并没有真正解决 OP 的问题。 --> +1。恕我直言,如果以后应该注销匿名代表,就不应该使用它们。保留它们是愚蠢的——>更好地使用方法。仅删除调用列表中的一些委托是非常随机且无用的。如果我错了纠正我。 :)
      【解决方案7】:

      有点蹩脚的方法:

      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. 需要时,将它们全部清除并重新添加其他的。

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

      【讨论】:

      • 如果你觉得它很蹩脚,请不要发布。
      • 即使是蹩脚的回应也可能对某人有价值。在这种情况下,至少有 6 人认为这种方法值得一票,尽管作者认为它很蹩脚。
      【解决方案8】:

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

      【讨论】:

        【解决方案9】:

        一个简单的解决方案:

        只需将事件句柄变量作为参数传递给自身。 如果你有由于多线程而无法访问原始创建变量的情况,你可以使用这个:

        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 会怎样?如果可能,您将遇到错误。但我不确定是不是这样。
        【解决方案10】:

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

        【讨论】:

          【解决方案11】:

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

          在此示例中,我必须使用匿名方法来包含一组 DataGridView 的 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);
                  }
              }
          }
          

          【讨论】:

            【解决方案12】:

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

            【讨论】:

              【解决方案13】:

              我最近在一个 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;
                      }
                  }
              }
              

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

              【讨论】:

                猜你喜欢
                • 2011-09-12
                • 2018-08-17
                • 2012-04-02
                • 1970-01-01
                • 2021-01-28
                • 2021-06-14
                • 1970-01-01
                • 2012-02-06
                • 1970-01-01
                相关资源
                最近更新 更多