Анонимный метод отписаться в C#

можно ли отказаться от подписки на анонимный метод из события?

Если я подписываюсь на такое событие:

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

MyEvent += MyMethod;

Я могу отменить подписку следующим образом:

MyEvent -= MyMethod;

но если я подписываюсь с помощью анонимного метода:

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

можно ли отказаться от подписки на этот анонимный метод? Если да, то как?

11 ответов


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

MyEvent += myDelegate;


// .... later

MyEvent -= myDelegate;

просто держите ссылку на делегата вокруг.


один из методов-объявить переменную для хранения анонимного метода, который затем будет доступен внутри самого анонимного метода. Это сработало для меня, потому что желаемым поведением было отказаться от подписки после обработки события.

пример:

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

из памяти спецификация явно не гарантирует поведение в любом случае, когда дело доходит до эквивалентности делегатов, созданных анонимными методами.

Если вам нужно отказаться от подписки, вы должны использовать "обычный" метод или сохранить делегат где-то еще, чтобы вы могли отказаться от подписки с точно таким же делегатом, который вы использовали для подписки.


В 3.0 можно сократить до:

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

вместо сохранения ссылки на любой делегат вы можете использовать свой класс, чтобы вернуть список вызовов события вызывающему. В основном вы можете написать что-то вроде этого (предполагая, что 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 здесь.


вид хромого подхода:

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. при необходимости очистите их все и повторно добавьте другие.

Это может не работать или быть наиболее эффективным методом, но должно выполнить работу.


С в C# 7.0 локальные функции функция была выпущена, подход предложил by J сбыл становится очень аккуратным.

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

Итак, честно говоря, у вас нет анонимной функции в качестве переменной здесь. Но я полагаю, что мотивация использовать его в вашем случае может быть применена к локальным функциям.


Если вы хотите иметь возможность контролировать отписку, вам нужно пройти маршрут, указанный в принятом ответе. Однако, если вы просто обеспокоены очисткой ссылок, когда ваш класс подписки выходит за рамки, тогда есть другое (слегка запутанное) решение, которое включает использование слабых ссылок. Я только что опубликовал вопрос-ответ на эту тему.


одно простое решение:

просто передайте переменную 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!");
}

Если вы хотите обратиться к какому-либо объекту с этим делегатом, возможно, вы можете использовать делегат.Вызове(тип, цель, объект, объект methodinfo объект methodinfo ) .net считает делегат равным по target и methodInfo


если лучший способ-сохранить ссылку на подписанный eventHandler, это может быть достигнуто с помощью словаря.

в этом примере я должен использовать анонимный метод для включения параметра mergeColumn для набора DataGridViews.

использование метода 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);
        }
    }
}