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

222

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

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

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

MyEvent += MyMethod;

Я могу отменить подписку, как это:

MyEvent -= MyMethod;

Но если я подпишусь, используя анонимный метод:

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

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

Эрик
источник
4
Что касается того, почему вы не можете сделать это: stackoverflow.com/a/25564492/23354
Марк Гравелл

Ответы:

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

MyEvent += myDelegate;


// .... later

MyEvent -= myDelegate;

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

Джейкоб Кралл
источник
141

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

Пример:

MyEventHandler foo = null;
foo = delegate(object s, MyEventArgs ev)
    {
        Console.WriteLine("I did it!");
        MyEvent -= foo;
    };
MyEvent += foo;
J c
источник
1
Используя такой код, Resharper жалуется на доступ к измененному замыканию ... надежен ли этот подход? Я имею в виду, уверены ли мы, что переменная 'foo' внутри тела анонимного метода действительно ссылается на сам анонимный метод?
BladeWise
7
Я нашел ответ на мой дабт, и именно в 'foo' будет действительно ссылка на анонимный метод itslef. Перехваченная переменная модифицируется, поскольку она перехватывается до того, как ей назначается анонимный метод.
BladeWise
2
Это именно то, что мне было нужно! Я пропустил = ноль. (MyEventHandler foo = делегат {... MyEvent- = foo;}; MyEvent + = foo; не работает ...)
TDaver
Resharper 6.1 не будет жаловаться, если вы объявите его как массив. Кажется немного странным, но я собираюсь слепо доверять своим инструментам в этом: MyEventHandler [] foo = {null}; foo [0] = ... {... MyEvent - = foo [0]; }; MyEvent + = foo [0];
Майк Пост
21

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

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

Джон Скит
источник
Я Джон, что ты имеешь в виду? Я не понимаю решение, представленное "J c", не будет работать должным образом?
Эрик Оуэлл
@EricOuellet: Этот ответ в основном является реализацией «сохранить делегата где-то еще, чтобы вы могли отписаться точно таким же делегатом, который вы использовали для подписки».
Джон Скит
Джон, извини, я прочитал твой ответ много раз, пытаясь выяснить, что ты имеешь в виду и где решение "J c" не использует один и тот же делегат для подписки и отписки, но я не могу его придумать. Возможно, вы можете указать мне на статью, которая объясняет, что вы говорите? Я знаю о вашей репутации, и мне бы очень хотелось понять, что вы имеете в виду, все, на что вы можете сослаться, будет очень полезно.
Эрик Ouellet
1
Я нашел: msdn.microsoft.com/en-us/library/ms366768.aspx, но они не рекомендуют использовать анонимный доступ, но не говорят, что есть какая-то серьезная проблема?
Эрик Ouellet
Я нашел это ... Большое спасибо (см. Ответ Майкла Блума
Эрик
16

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

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

источник
15

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

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

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

mazharenko
источник
1
Чтобы сделать читабельность еще лучше, вы можете переместить MyEvent + = foo; строка должна быть до объявления foo.
Марк Жуковский
9

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

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

Hemme
источник
2
Означает ли это, что я мог бы случайно отписать другой экземпляр (т.е. не я) от события, если они подписались после меня?
Дамблдад
@Dumbledad Конечно, это всегда отменяет регистрацию последнего зарегистрированного. Если вы хотите динамически отписаться от определенного анонимного делегата, вам нужно как-то его идентифицировать. Я бы посоветовал сохранить ссылку :)
LuckyLikey
Довольно круто, что вы делаете, но я не могу представить ни одного случая, когда это могло бы быть полезным. Но я действительно не решаю вопрос ОП. -> +1. ИМХО, просто не следует использовать анонимных делегатов, если они будут сняты с регистрации позже. Держать их глупо -> лучше использовать метод. Удаление только одного делегата из списка Invocation довольно случайно и бесполезно. Поправьте меня если я ошибаюсь. :)
LuckyLikey
6

Вид отстойного подхода:

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

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

casademora
источник
14
Если вы думаете, что это хромое, не публикуйте
Джерри Никсон
2

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

Benjol
источник
2

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

просто передайте переменную 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!");
}
Мануэль Мархольд
источник
Что делать, если MyEvent вызывается дважды до запуска MyEvent -= myEventHandlerInstance;? Если это возможно, у вас будет ошибка. Но я не уверен, что это так.
LuckyLikey
0

если вы хотите обратиться к какому-либо объекту с этим делегатом, возможно, вы можете использовать Delegate.CreateDelegate (Type, Object target, MethodInfo methodInfo) .net считают, что делегат равен по target и methodInfo

user3217549
источник
0

Если лучший способ - сохранить ссылку на подписанный 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);
        }
    }
}
Larry
источник