Отменить подписку анонимным методом в C#


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

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

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

MyEvent += MyMethod;

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

MyEvent -= MyMethod;

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

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

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

11 199

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();

Я написал Полный пост об этой технике здесь.

какой-то ущербный подход:

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 c становится очень аккуратным.

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 считает делегат равным по цели и 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);
        }
    }
}