Bagaimana saya bisa menghapus langganan acara dalam C #?

141

Ambil kelas C # berikut:

c1 {
 event EventHandler someEvent;
}

Jika ada banyak langganan untuk c1's someEventacara dan saya ingin menghapus mereka semua, apa cara terbaik untuk mencapai ini? Juga pertimbangkan bahwa berlangganan ke acara ini bisa / adalah delegasi lambdas / anonim.

Saat ini solusi saya adalah menambahkan ResetSubscriptions()metode ke c1set someEventke nol. Saya tidak tahu apakah ini memiliki konsekuensi yang tidak terlihat.

programmer
sumber

Jawaban:

181

Dari dalam kelas, Anda dapat mengatur variabel (tersembunyi) menjadi nol. Referensi nol adalah cara kanonik untuk merepresentasikan daftar doa yang kosong, secara efektif.

Dari luar kelas, Anda tidak dapat melakukan ini - acara pada dasarnya mengekspos "berlangganan" dan "berhenti berlangganan" dan hanya itu.

Layak untuk mengetahui apa yang sebenarnya terjadi seperti bidang - mereka membuat variabel dan acara pada saat yang sama. Di dalam kelas, Anda akhirnya merujuk variabel. Dari luar, Anda merujuk acara tersebut.

Lihat artikel saya tentang acara dan delegasi untuk informasi lebih lanjut.

Jon Skeet
sumber
3
Jika Anda keras kepala, Anda bisa memaksanya jernih melalui refleksi. Lihat stackoverflow.com/questions/91778/… .
Brian
1
@Brian: Tergantung implementasinya. Jika itu hanya acara atau bidang EventHandlerList, Anda mungkin bisa. Anda harus mengenali kedua kasus itu - dan mungkin ada sejumlah implementasi lainnya.
Jon Skeet
@ Yosua: Tidak, itu akan mengatur variabel untuk memiliki nilai nol. Saya setuju bahwa variabel tidak akan dipanggil hidden.
Jon Skeet
@ JonSkeet Itulah yang saya (pikir) saya katakan. Cara itu ditulis membingungkan saya selama 5 menit.
@ JoshuaLamusga: Ya Anda mengatakan itu akan menghapus daftar doa, yang terdengar seperti memodifikasi objek yang ada.
Jon Skeet
34

Tambahkan metode ke c1 yang akan mengatur 'someEvent' ke nol.

public class c1
{
    event EventHandler someEvent;
    public ResetSubscriptions() => someEvent = null;    
}
programmer
sumber
Itulah perilaku yang saya lihat. Seperti yang saya katakan dalam pertanyaan saya, saya tidak tahu apakah saya mengabaikan sesuatu.
programmer
8
class c1
{
    event EventHandler someEvent;
    ResetSubscriptions() => someEvent = delegate { };
}

Lebih baik menggunakan delegate { }daripada nullmenghindari pengecualian ref kosong.

Feng
sumber
2
Mengapa? Bisakah Anda memperluas jawaban ini?
S. Buda
1
@ S.Buda Karena jika itu nol maka Anda akan mendapatkan null ref. Ini seperti menggunakan List.Clear()vs myList = null.
AustinWBryan
6

Mengatur acara ke nol di dalam karya kelas. Saat Anda membuang kelas, Anda harus selalu mengatur acara ke nol, GC memiliki masalah dengan acara dan mungkin tidak membersihkan kelas yang dibuang jika memiliki acara yang menggantung.

Jonathan C Dickinson
sumber
6

Praktik terbaik untuk menghapus semua pelanggan adalah mengatur someEvent ke nol dengan menambahkan metode publik lain jika Anda ingin mengekspos fungsi ini ke luar. Ini tidak memiliki konsekuensi yang tak terlihat. Syaratnya adalah untuk mengingat untuk mendeklarasikan SomeEvent dengan kata kunci 'event'.

Silakan lihat buku - C # 4.0 singkatnya, halaman 125.

Seseorang di sini mengusulkan untuk menggunakan Delegate.RemoveAllmetode. Jika Anda menggunakannya, kode sampel dapat mengikuti formulir di bawah ini. Tapi itu benar-benar bodoh. Kenapa tidak SomeEvent=nulldi dalam ClearSubscribers()fungsinya saja?

public void ClearSubscribers ()
{
   SomeEvent = (EventHandler) Delegate.RemoveAll(SomeEvent, SomeEvent);
   // Then you will find SomeEvent is set to null.
}
Cary
sumber
5

Anda dapat mencapai ini dengan menggunakan metode Delegate.Remove atau Delegate.RemoveAll.

Mikha
sumber
6
Saya tidak percaya ini akan bekerja dengan ekspresi lambda atau delegasi anonim.
programmer
3

Komentar membosankan yang diperpanjang secara konseptual.

Saya lebih suka menggunakan kata "event handler" daripada "event" atau "delegate". Dan menggunakan kata "event" untuk hal-hal lain. Dalam beberapa bahasa pemrograman (VB.NET, Object Pascal, Objective-C), "event" disebut "pesan" atau "sinyal", dan bahkan memiliki kata kunci "pesan", dan sintaksis gula spesifik.

const
  WM_Paint = 998;  // <-- "question" can be done by several talkers
  WM_Clear = 546;

type
  MyWindowClass = class(Window)
    procedure NotEventHandlerMethod_1;
    procedure NotEventHandlerMethod_17;

    procedure DoPaintEventHandler; message WM_Paint; // <-- "answer" by this listener
    procedure DoClearEventHandler; message WM_Clear;
  end;

Dan, untuk menanggapi "pesan" itu, seorang "pengendali acara" merespons, apakah delegasi tunggal atau banyak delegasi.

Rangkuman: "Acara" adalah "pertanyaan", "pengendali acara" adalah jawabannya.

umlcat
sumber
1

Ini solusi saya:

public class Foo : IDisposable
{
    private event EventHandler _statusChanged;
    public event EventHandler StatusChanged
    {
        add
        {
            _statusChanged += value;
        }
        remove
        {
            _statusChanged -= value;
        }
    }

    public void Dispose()
    {
        _statusChanged = null;
    }
}

Anda perlu menelepon Dispose()atau menggunakan using(new Foo()){/*...*/}pola untuk berhenti berlangganan semua anggota daftar doa.

Jalal
sumber
0

Hapus semua acara, anggap acara adalah tipe "Aksi":

Delegate[] dary = TermCheckScore.GetInvocationList();

if ( dary != null )
{
    foreach ( Delegate del in dary )
    {
        TermCheckScore -= ( Action ) del;
    }
}
Googol
sumber
1
Jika Anda berada di dalam tipe yang menyatakan acara yang tidak perlu Anda lakukan, Anda bisa mengaturnya menjadi nol, jika Anda berada di luar tipe maka Anda tidak bisa mendapatkan daftar doa dari delegasi. Juga, kode Anda melempar pengecualian jika acara tersebut nol, saat memanggil GetInvocationList.
Servy
-1

Alih-alih menambahkan dan menghapus callback secara manual dan memiliki banyak jenis delegasi yang dideklarasikan di mana-mana:

// The hard way
public delegate void ObjectCallback(ObjectType broadcaster);

public class Object
{
    public event ObjectCallback m_ObjectCallback;
    
    void SetupListener()
    {
        ObjectCallback callback = null;
        callback = (ObjectType broadcaster) =>
        {
            // one time logic here
            broadcaster.m_ObjectCallback -= callback;
        };
        m_ObjectCallback += callback;

    }
    
    void BroadcastEvent()
    {
        m_ObjectCallback?.Invoke(this);
    }
}

Anda dapat mencoba pendekatan umum ini:

public class Object
{
    public Broadcast<Object> m_EventToBroadcast = new Broadcast<Object>();

    void SetupListener()
    {
        m_EventToBroadcast.SubscribeOnce((ObjectType broadcaster) => {
            // one time logic here
        });
    }

    ~Object()
    {
        m_EventToBroadcast.Dispose();
        m_EventToBroadcast = null;
    }

    void BroadcastEvent()
    {
        m_EventToBroadcast.Broadcast(this);
    }
}


public delegate void ObjectDelegate<T>(T broadcaster);
public class Broadcast<T> : IDisposable
{
    private event ObjectDelegate<T> m_Event;
    private List<ObjectDelegate<T>> m_SingleSubscribers = new List<ObjectDelegate<T>>();

    ~Broadcast()
    {
        Dispose();
    }

    public void Dispose()
    {
        Clear();
        System.GC.SuppressFinalize(this);
    }

    public void Clear()
    {
        m_SingleSubscribers.Clear();
        m_Event = delegate { };
    }

    // add a one shot to this delegate that is removed after first broadcast
    public void SubscribeOnce(ObjectDelegate<T> del)
    {
        m_Event += del;
        m_SingleSubscribers.Add(del);
    }

    // add a recurring delegate that gets called each time
    public void Subscribe(ObjectDelegate<T> del)
    {
        m_Event += del;
    }

    public void Unsubscribe(ObjectDelegate<T> del)
    {
        m_Event -= del;
    }

    public void Broadcast(T broadcaster)
    {
        m_Event?.Invoke(broadcaster);
        for (int i = 0; i < m_SingleSubscribers.Count; ++i)
        {
            Unsubscribe(m_SingleSubscribers[i]);
        }
        m_SingleSubscribers.Clear();
    }
}
barthdamon
sumber
Bisakah Anda memformat pertanyaan Anda dan menghapus semua ruang putih di sebelah kiri? Ketika Anda menyalin dan menempel dari IDE ini bisa terjadi
AustinWBryan
Baru saja menyingkirkan ruang putih itu, badanku
barthdamon