代码之家  ›  专栏  ›  技术社区  ›  Eric

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

  •  252
  • Eric  · 技术社区  · 17 年前

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

    如果我订阅这样的活动:

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

    我可以这样取消订阅:

    MyEvent -= MyMethod;
    

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

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

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

    14 回复  |  直到 17 年前
        1
  •  236
  •   Jacob Krall    14 年前
    Action myDelegate = delegate(){Console.WriteLine("I did it!");};
    
    MyEvent += myDelegate;
    
    
    // .... later
    
    MyEvent -= myDelegate;
    

    只需随身携带代表的参考资料。

        2
  •  147
  •   J c    17 年前

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

    例子:

    MyEventHandler foo = null;
    foo = delegate(object s, MyEventArgs ev)
        {
            Console.WriteLine("I did it!");
            MyEvent -= foo;
        };
    MyEvent += foo;
    
        3
  •  23
  •   Jon Skeet    17 年前

    自从 C# 7.0 local functions 功能已经发布,方法 suggested 靠近 J c 变得非常整洁。

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

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

        4
  •  23
  •   jjxtra    7 年前

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

    如果您需要取消订阅,您应该使用“正常”方法或将代理保留在其他地方,这样您就可以使用与您以前订阅的代理完全相同的代理进行取消订阅。

        5
  •  17
  •   yaniv yaniv    16 年前

    在3.0中可以缩短为:

    MyHandler myDelegate = ()=>Console.WriteLine("I did it!");
    MyEvent += myDelegate;
    ...
    MyEvent -= myDelegate;
    
        6
  •  10
  •   hemme    15 年前

    您可以对类进行检测,以便将事件的调用列表返回给调用者,而不是保留对任何委托的引用。基本上你可以写这样的东西(假设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 .

        7
  •  6
  •   casademora    17 年前

    一种蹩脚的方法:

    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. 必要时,将它们全部清除,然后重新添加其他内容。

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

        8
  •  2
  •   Community Mohan Dere    9 年前

    一个简单的解决方案:

    只需将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!");
    }
    
        9
  •  2
  •   Manuel Marhold    11 年前

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

        10
  •  0
  •   user3217549    12 年前

    如果最好的方法是在订阅的事件处理程序上保留引用,则可以使用字典来实现。

    在这个例子中,我必须使用匿名方法来包含一组ViewModel的mergeColumn参数。

    使用MergeColumn方法,将enable参数设置为true,启用事件,同时将其与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);
            }
        }
    }
    
        11
  •  0
  •   Larry    9 年前

    如果你想用这个委托引用某个对象,你可以使用委托。CreateDelegate(类型、对象目标、方法信息MethodInfo) .net认为委托等于target和methodInfo

        12
  •  0
  •   unggyu    5 年前

    有一种方法可以通过自己实现闭包而不是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
  •  0
  •   Bobby Oster    4 年前

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