Berhenti berlangganan metode anonim di C #

222

Apakah mungkin untuk berhenti berlangganan metode anonim dari suatu peristiwa?

Jika saya berlangganan acara seperti ini:

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

MyEvent += MyMethod;

Saya dapat berhenti berlangganan seperti ini:

MyEvent -= MyMethod;

Tetapi jika saya berlangganan menggunakan metode anonim:

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

apakah mungkin untuk berhenti berlangganan metode anonim ini? Jika ya, bagaimana caranya?

Eric
sumber
4
Adapun mengapa Anda tidak dapat melakukan ini: stackoverflow.com/a/25564492/23354
Marc Gravell

Jawaban:

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

MyEvent += myDelegate;


// .... later

MyEvent -= myDelegate;

Simpan saja referensi ke delegasi sekitar.

Jacob Krall
sumber
141

Salah satu teknik adalah mendeklarasikan variabel untuk menyimpan metode anonim yang kemudian akan tersedia di dalam metode anonim itu sendiri. Ini berhasil bagi saya karena perilaku yang diinginkan adalah berhenti berlangganan setelah acara ditangani.

Contoh:

MyEventHandler foo = null;
foo = delegate(object s, MyEventArgs ev)
    {
        Console.WriteLine("I did it!");
        MyEvent -= foo;
    };
MyEvent += foo;
J c
sumber
1
Menggunakan kode semacam ini, Resharper mengeluh tentang mengakses penutupan yang dimodifikasi ... apakah pendekatan ini dapat diandalkan? Maksud saya, apakah kita yakin bahwa variabel 'foo' di dalam tubuh metode anonim, benar-benar merujuk pada metode anonim itu sendiri?
BladeWise
7
Saya menemukan jawaban untuk dubt saya, dan itu adalah 'foo' akan benar-benar memegang referensi ke metode anonim itslef. Variabel yang ditangkap dimodifikasi, karena ditangkap sebelum metode anonim ditugaskan untuk itu.
BladeWise
2
Itulah yang saya butuhkan! Saya melewatkan = null. (MyEventHandler foo = delegate {... MyEvent- = foo;}; MyEvent + = foo; tidak berfungsi ...)
TDaver
Resharper 6.1 tidak mengeluh jika Anda mendeklarasikannya sebagai sebuah array. Agak aneh, tapi saya akan mempercayai alat saya secara membabi buta ini: MyEventHandler [] foo = {null}; foo [0] = ... {... MyEvent - = foo [0]; }; MyEvent + = foo [0];
Mike Post
21

Dari memori, spesifikasi secara eksplisit tidak menjamin perilaku dengan cara apa pun ketika menyangkut kesetaraan delegasi yang dibuat dengan metode anonim.

Jika Anda harus berhenti berlangganan, Anda harus menggunakan metode "normal" atau mempertahankan delegasi di tempat lain sehingga Anda dapat berhenti berlangganan dengan delegasi yang sama persis seperti yang Anda gunakan untuk berlangganan.

Jon Skeet
sumber
I Jon, apa maksudmu? Saya tidak mengerti. apakah solusi yang diekspos oleh "Jc" tidak akan berfungsi dengan baik?
Eric Ouellet
@EricOuellet: Jawaban itu pada dasarnya merupakan implementasi dari "simpan delegasi di tempat lain sehingga Anda dapat berhenti berlangganan dengan delegasi yang sama persis seperti yang Anda gunakan untuk berlangganan".
Jon Skeet
Jon, saya minta maaf, saya membaca jawaban Anda berkali-kali mencoba mencari tahu apa yang Anda maksud dan di mana solusi "Jc" tidak menggunakan delegasi yang sama untuk berlangganan dan berhenti berlangganan, tetapi saya tidak bisa melakukannya. Mungkin Anda bisa mengarahkan saya ke artikel yang menjelaskan apa yang Anda katakan? Saya tahu tentang reputasi Anda dan saya benar-benar ingin memahami maksud Anda, apa pun yang Anda tautkan akan sangat dihargai.
Eric Ouellet
1
Saya menemukan: msdn.microsoft.com/en-us/library/ms366768.aspx tetapi mereka merekomendasikan tidak menggunakan anonim tetapi mereka tidak mengatakan bahwa ada masalah besar?
Eric Ouellet
Saya menemukannya ... Terima kasih banyak (lihat jawaban Michael Blome): social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/…
Eric Ouellet
16

In 3.0 dapat disingkat menjadi:

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

sumber
15

Karena fitur fungsi lokal C # 7.0 telah dirilis, pendekatan yang disarankan oleh Jc menjadi sangat rapi.

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

Jadi, jujur, Anda tidak memiliki fungsi anonim sebagai variabel di sini. Tapi saya kira motivasi untuk menggunakannya dalam kasus Anda dapat diterapkan pada fungsi lokal.

Mazharenko
sumber
1
Untuk membuat keterbacaan lebih baik, Anda dapat memindahkan MyEvent + = foo; baris sebelum deklarasi foo.
Mark Zhukovsky
9

Alih-alih menyimpan referensi untuk delegasi apa pun, Anda dapat mengatur kelas Anda untuk memberikan daftar doa acara kembali ke pemanggil. Pada dasarnya Anda dapat menulis sesuatu seperti ini (dengan asumsi bahwa MyEvent dideklarasikan di dalam MyClass):

public class MyClass 
{
  public event EventHandler MyEvent;

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

Jadi, Anda dapat mengakses seluruh daftar doa dari luar MyClass dan berhenti berlangganan penangan yang Anda inginkan. Misalnya:

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

Saya sudah menulis posting lengkap tentang teknik ini di sini .

hemme
sumber
2
Apakah ini berarti saya secara tidak sengaja dapat berhenti berlangganan contoh yang berbeda (yaitu bukan saya) dari acara tersebut jika mereka berlangganan setelah saya?
dumbledad
@dumbledad tentu saja ini akan selalu membatalkan pendaftaran yang terakhir terdaftar. Jika Anda ingin berhenti berlangganan delegasi anonim tertentu secara dinamis, Anda harus mengidentifikasinya. Saya sarankan menyimpan referensi kemudian :)
LuckyLikey
Cukup keren, apa yang Anda lakukan, tapi saya tidak bisa membayangkan satu kasus di mana ini bisa berguna. Tapi saya tidak benar-benar menyelesaikan Pertanyaan OP. -> +1. IMHO, seseorang seharusnya tidak menggunakan delegasi anonim jika mereka harus dideregistrasi nanti. Menjaga mereka bodoh -> lebih baik menggunakan Metode. Menghapus hanya beberapa delegasi dalam daftar Doa sangat acak dan tidak berguna. Perbaiki saya jika saya salah. :)
LuckyLikey
6

Jenis pendekatan lumpuh:

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

  ...

  public event Action OnDoSomething
  {
    add {
      _eventList.Add(value);
    }
    remove {
      _eventList.Remove(value);
    }
  }
}
  1. Mengganti acara menambah / menghapus metode.
  2. Simpan daftar penangan acara tersebut.
  3. Bila perlu, bersihkan semuanya dan tambahkan kembali yang lain.

Ini mungkin tidak bekerja atau metode yang paling efisien, tetapi harus menyelesaikan pekerjaan.

casademora
sumber
14
Jika Anda pikir itu lumpuh, jangan mempostingnya.
Jerry Nixon
2

Jika Anda ingin dapat mengendalikan berhenti berlangganan maka Anda harus pergi rute yang ditunjukkan dalam jawaban yang Anda terima. Namun, jika Anda hanya khawatir tentang membersihkan referensi ketika kelas berlangganan Anda keluar dari ruang lingkup, maka ada solusi lain (sedikit berbelit-belit) yang melibatkan menggunakan referensi lemah. Saya baru saja memposting pertanyaan dan jawaban tentang topik ini.

Benjol
sumber
2

Satu solusi sederhana:

cukup kirimkan variabel eventhandle sebagai parameter ke dirinya sendiri. Acara jika Anda memiliki kasus bahwa Anda tidak dapat mengakses variabel yang dibuat asli karena multithreading, Anda dapat menggunakan ini:

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!");
}
Manuel Marhold
sumber
bagaimana jika MyEvent dipanggil dua kali, sebelum MyEvent -= myEventHandlerInstance;dijalankan? Jika memungkinkan, Anda akan mengalami kesalahan. Tetapi saya tidak yakin apakah itu masalahnya.
LuckyLikey
0

jika Anda ingin merujuk ke beberapa objek dengan delegasi ini, mungkin Anda dapat menggunakan Delegate.CreateDelegate (Tipe, Target objek, MethodInfo methodInfo) .net menganggap delegasi sama dengan target dan methodInfo

pengguna3217549
sumber
0

Jika cara terbaik adalah dengan menyimpan referensi pada eventHandler berlangganan, ini dapat dicapai dengan menggunakan Kamus.

Dalam contoh ini, saya harus menggunakan metode anonim untuk memasukkan parameter mergeColumn untuk satu set DataGridViews.

Menggunakan metode MergeColumn dengan mengaktifkan parameter yang disetel ke true memungkinkan acara saat menggunakannya dengan false menonaktifkannya.

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
sumber