2008-10-08 12 views
192

Czy można wypisać anonimową metodę ze zdarzenia?Anuluj subskrypcję metody anonimowej w języku C#

Gdybym zapisać się do takiego wydarzenia to:

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

MyEvent += MyMethod; 

mogę Zrezygnuj tak:

MyEvent -= MyMethod; 

Ale jeśli mogę zapisać za pomocą anonimowej metody:

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

Czy można zrezygnować z tej anonimowej metody? Jeśli tak to jak?

+1

Co do * dlaczego * nie możesz tego zrobić: http://stackoverflow.com/a/25564492/23354 –

Odpowiedz

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

MyEvent += myDelegate; 


// .... later 

MyEvent -= myDelegate; 

Po prostu zachowaj odwołanie do delegata.

+0

Usunięto mój poprzedni komentarz. Teraz widzę ... "delegate' delegate() 'są opcjonalne. –

+1

Ten kod ... jak jest ... nie kompiluje się. Czy czegoś brakuje? – BeemerGuy

+3

@BeemerGuy: 'var' nie może przyjąć anonimowej metody, więc musisz jawnie zadeklarować typ' myDelegate' - w tym przypadku zaktualizowałem odpowiedź, aby użyć 'Action'. –

17

Specyfikacja z definicji nie gwarantuje zachowania w żaden sposób, jeśli chodzi o równoważność delegatów utworzonych za pomocą metod anonimowych.

Jeśli chcesz zrezygnować z subskrypcji, użyj metody "normalnej" lub zatrzymaj delegata gdzie indziej, aby móc zrezygnować z subskrypcji dokładnie tego samego uczestnika, którego użyto do zasubskrybowania.

+0

I Jon, co ty mówisz? Nie rozumiem. czy rozwiązanie odsłonięte przez "J c" nie działa poprawnie? –

+0

@EricOuellet: Ta odpowiedź jest w zasadzie implementacją "zatrzymaj delegata gdzie indziej, aby móc wypisać się z tym samym delegatem, którego użyłeś do subskrybowania". –

+0

Jon, przepraszam, wielokrotnie czytałem twoją odpowiedź, próbując dowiedzieć się, co masz na myśli i gdzie rozwiązanie "J c" nie używa tego samego delegata do subskrybowania i rezygnacji z subskrypcji, ale nie mogę tego znieść. Może możesz wskazać mi artykuł, który wyjaśni ci, co mówisz? Wiem o twojej reputacji i naprawdę chciałbym zrozumieć, co masz na myśli, wszystko, co możesz połączyć, byłoby naprawdę doceniane. –

6

Rodzaju lame podejścia:

public class SomeClass 
{ 
    private readonly IList<Action> _eventList = new List<Action>(); 

    ... 

    public event Action OnDoSomething 
    { 
    add { 
     _eventList.Add(value); 
    } 
    remove { 
     _eventList.Remove(value); 
    } 
    } 
} 
  1. przesłonić zdarzenie dodać/usunąć metodami.
  2. Zachowaj listę tych programów do obsługi zdarzeń.
  3. W razie potrzeby wyczyść je wszystkie i dodaj ponownie pozostałe.

To może nie działać lub być najbardziej wydajną metodą, ale powinno się wykonać zadanie.

+10

Jeśli uważasz, że jest kulawy, nie publikuj go. –

133

Jedną z technik jest zadeklarowanie zmiennej do przechowywania anonimowej metody, która byłaby wtedy dostępna w samej anonimowej metodzie. To działało dla mnie, ponieważ pożądane zachowanie polegało na rezygnacji z subskrypcji po rozpatrzeniu zdarzenia.

Przykład:

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

Używając tego rodzaju kodu, Resharper narzeka na dostęp do zmodyfikowanego zamknięcia ... czy to podejście jest niezawodne? Mam na myśli, czy jesteśmy pewni, że zmienna 'foo' w ciele metody anonimowej naprawdę odwołuje się do samej anonimowej metody? – BladeWise

+7

Znalazłem odpowiedź na mój dubt i to jest to, że "foo" naprawdę będzie zawierało odniesienie do anonimowej metody hislef. Przechwycona zmienna jest modyfikowana, ponieważ jest przechwytywana przed przypisaniem jej anonimowej metody. – BladeWise

+2

Dokładnie tego potrzebowałem! Brakowało mi = null. (MyEventHandler foo = delegować {... MyEvent- = foo;}; MyEvent + = foo; nie działało ...) – TDaver

16

w 3,0 może być skrócony do:

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

Jeśli chcesz być w stanie kontrolować anulowanie subskrypcji to trzeba iść drogą wskazaną w akceptowanych odpowiedź. Jeśli jednak chodzi ci tylko o wyczyszczenie referencji, gdy twoja subskrybująca klasa wykracza poza zakres, istnieje inne (nieco zawiłe) rozwiązanie, które wymaga użycia słabych referencji. Właśnie opublikowałem question and answer na ten temat.

9

Zamiast zachowywać odwołanie do dowolnego delegata, możesz przyrządzić swoją klasę, aby przywrócić dzwoniącemu listę zdarzeń. W zasadzie można napisać coś takiego (zakładając, że MyEvent jest zadeklarowana wewnątrz MojaKlasa):

public class MyClass 
{ 
    public event EventHandler MyEvent; 

    public IEnumerable<EventHandler> GetMyEventHandlers() 
    { 
     return from d in MyEvent.GetInvocationList() 
      select (EventHandler)d; 
    } 
} 

czemu można uzyskać dostęp do całej listy wywołania z zewnątrz MojaKlasa i wypisania żadnego obsługi chcesz.Na przykład:

myClass.MyEvent -= myClass.GetMyEventHandlers().Last(); 

Napisałem pełen post o tym tecnique here.

+2

Czy to oznacza, że ​​mogę przypadkowo zrezygnować z subskrypcji innej instancji (np. Nie mnie) z wydarzenia, jeśli subskrybują one po mnie? – dumbledad

+0

@dumbledad oczywiście to wylogowuje z rejestru ostatniego zarejestrowanego. Jeśli chcesz dynamicznie anulować subskrypcję określonego anonimowego uczestnika, musisz go jakoś zidentyfikować. Proponuję zachować referencję wtedy :) – LuckyLikey

+0

To jest całkiem fajne, co robisz, ale nie mogę sobie wyobrazić jednego przypadku, w którym mogłoby to być użyteczne. Ale tak naprawdę nie rozwiążę problemu PO. -> +1. IMHO, po prostu nie należy korzystać z anonimowych delegatów, jeśli później zostaną wyrejestrowani. Utrzymanie ich jest głupie -> lepsze wykorzystanie metody. Usunięcie tylko niektórych delegatów z listy Invocation jest całkiem przypadkowe i bezużyteczne. Popraw mnie, jeśli jestem zła. :) – LuckyLikey

0

jeśli chcesz odnieść się do jakiegoś obiektu z tego delegata, może można użyć Delegate.CreateDelegate (rodzaj, cel Object, MethodInfo MethodInfo) .net rozważyć delegat równa się przez cel i MethodInfo

1

Jedno proste rozwiązanie :

wystarczy przekazać zmienną eventhandle jako parametr do samego siebie. Event jeśli masz sprawę, że nie można uzyskać dostęp do oryginalnej zmiennej utworzonej z powodu wielowątkowość, można użyć to:

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!"); 
} 
+0

co jeśli MyEvent zostanie wywołany dwa razy, zanim uruchomi się 'MyEvent - = myEventHandlerInstance;' Jeśli to możliwe, wystąpiłby błąd. Ale nie jestem pewien, czy tak jest. – LuckyLikey

0

Jeśli najlepszym sposobem jest, aby zachować odniesienie na subskrybowanych Podprogram ten można osiągnąć stosując słownik.

W tym przykładzie muszę użyć metody anonimowej do uwzględnienia parametru mergeColumn dla zestawu DataGridViews.

Użycie metody MergeColumn z ustawionym parametrem enable na wartość true powoduje, że zdarzenie podczas używania go z false powoduje jego dezaktywację.

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); 
     } 
    } 
} 
2

Od C# 7.0 local functions funkcja została wydana, podejście suggested przez J c staje się naprawdę fajnie.

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

Szczerze mówiąc, nie ma tu anonimowej funkcji jako zmiennej. Ale przypuszczam, że motywacja do użycia go w twoim przypadku może być zastosowana do lokalnych funkcji.