Скасувати підписку на анонімний метод у 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
Марк Gravell

Відповіді:


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;

1
Використовуючи такий тип коду, Решарпер скаржиться на доступ до модифікованого закриття ... чи такий підхід надійний? Я маю на увазі, чи ми впевнені, що змінна 'foo' всередині тіла анонімного методу насправді посилається на сам анонімний метод?
BladeWise

7
Я знайшов відповідь на свій дабт, і саме те, що "foo" по-справжньому буде посилатися на анонімний метод itlef. Захоплена змінна модифікується, оскільки вона фіксується до того, як анонімний метод присвоюється їй.
BladeWise

2
Це саме те, що мені було потрібно! Я пропустив = null. (MyEventHandler foo = делегат {... MyEvent- = foo;}; MyEvent + = foo; не працював ...)
TDaver

Resharper 6.1 не скаржиться, якщо ви оголосите це як масив. Здається трохи дивно, але я збираюся сліпо довіряти своїм інструментам на цьому: MyEventHandler [] foo = {null}; foo [0] = ... {... MyEvent - = foo [0]; }; MyEvent + = foo [0];
Майк Пост

21

З пам'яті специфікація явно не гарантує поведінку в будь-якому випадку, коли мова йде про еквівалентність делегатів, створених анонімними методами.

Якщо вам потрібно скасувати підписку, вам слід скористатися "звичайним" методом або зберегти делегата десь в іншому місці, щоб ви могли скасувати передплату саме тим самим делегатом, який ви використовували для підписки.


I Jon, що ти любиш? Я не розумію. чи не буде належним чином працювати підданий "J c" розчин?
Ерік Оуеллет

@EricOuellet: Ця відповідь, в основному, є реалізацією "зберегти делегата десь в іншому місці, щоб ви могли скасувати підписку з таким самим делегатом, який ви використовували для підписки".
Джон Скіт

Джон, вибачте, я читав вашу відповідь багато разів, намагаючись розібратися, що ви маєте на увазі, і де рішення "J c" не використовує того самого делегата, щоб підписатися та скасувати підписку, але я не можу його знайти. Можливо, ви можете вказати мені на статтю, яка пояснює, що ви говорите? Я знаю про вашу репутацію, і я дуже хотів би зрозуміти, що ви маєте на увазі, все, що ви можете зв’язати, було б дуже вдячно.
Ерік Оуеллет

1
Я знайшов: msdn.microsoft.com/en-us/library/ms366768.aspx, але вони не рекомендують використовувати анонімні, але вони не кажуть, що є якась значна проблема?
Ерік Оуеллет

Я знайшов це ... Дякую (див. Відповідь Майкла Блома): social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/…
Ерік

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;

Отже, чесно кажучи, ви не маєте тут анонімної функції як змінної. Але я припускаю, що мотивація використовувати його у вашому випадку може бути застосована до локальних функцій.


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

Я написав повний пост про це tecnique тут .


2
Чи означає це, що я міг випадково відписати інший примірник (тобто не я) від події, якщо вони підписалися після мене?
dumbledad

@dumbledad звичайно, це завжди скасує останній зареєстрований. Якщо ви хотіли динамічно скасувати підписку на конкретного анонімного делегата, вам потрібно якось його ідентифікувати. Тоді я б запропонував зберегти посилання :)
LuckyLikey

Дуже круто, що ти робиш, але я не можу уявити жодного випадку, коли це може бути корисним. Але я справді не вирішує питання ОП. -> +1. ІМХО, просто не слід використовувати анонімних делегатів, якщо вони пізніше будуть скасовані. Зберігати їх нерозумно -> краще використовувати метод. Видалення лише делегата зі списку викликів є досить випадковим та марним. Виправте мене, якщо я помиляюся. :)
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. Якщо потрібно, очистіть їх усіх та додайте інші.

Це може не працювати або бути найбільш ефективним методом, але слід виконати роботу.


14
Якщо ви думаєте, що це кульгаво, не публікуйте це.
Джеррі Ніксон

2

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


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 (Тип, Об'єктна мета, MethodInfo methodInfo).


0

Якщо найкращим способом є збереження посилання на підписаний eventHandler, цього можна досягти, використовуючи словник.

У цьому прикладі я повинен використовувати анонімний метод, щоб включити параметр mergeColumn для набору DataGridViews.

Використання методу MergeColumn з параметром включення, встановленим на істинне, дозволяє події під час використання з помилковим відключенням.

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);
        }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.