event Action <> vs event EventHandler <>


144

Apakah ada perbedaan antara mendeklarasikan event Action<>dan event EventHandler<>.

Dengan asumsi tidak masalah objek apa yang sebenarnya memunculkan suatu peristiwa.

sebagai contoh:

public event Action<bool, int, Blah> DiagnosticsEvent;

vs.

public event EventHandler<DiagnosticsArgs> DiagnosticsEvent;

class DiagnosticsArgs : EventArgs
{
    public DiagnosticsArgs(bool b, int i, Blah bl)
    {...}
    ...
}

penggunaan akan hampir sama dalam kedua kasus:

obj.DiagnosticsEvent += HandleDiagnosticsEvent;

Ada beberapa hal yang saya tidak suka tentang event EventHandler<>pola:

  • Deklarasi tipe ekstra berasal dari EventArgs
  • Wajib lewat sumber objek - sering tidak ada yang peduli

Lebih banyak kode berarti lebih banyak kode untuk dipelihara tanpa keuntungan yang jelas.

Sebagai hasilnya, saya lebih suka event Action<>

Namun, hanya jika ada terlalu banyak tipe argumen dalam Aksi <>, maka kelas tambahan akan diperlukan.


2
plusOne (Saya baru saja mengalahkan sistem) untuk "tidak ada yang peduli"
hyankov

@plusOne: Saya sebenarnya perlu tahu pengirimnya! Katakan sesuatu terjadi dan Anda ingin tahu siapa yang melakukannya. Itu yang Anda butuhkan 'sumber objek' (alias pengirim).
Kamran Bigdely

pengirim dapat menjadi properti di payload acara
Thanasis Ioannidis

Jawaban:


67

Perbedaan utama adalah bahwa jika Anda menggunakan Action<>acara Anda tidak akan mengikuti pola desain hampir semua acara lain dalam sistem, yang saya anggap sebagai kelemahan.

Satu terbalik dengan pola desain yang mendominasi (terlepas dari kekuatan kesamaan) adalah Anda dapat memperluas EventArgsobjek dengan properti baru tanpa mengubah tanda tangan acara. Ini masih mungkin terjadi jika Anda menggunakan Action<SomeClassWithProperties>, tapi saya tidak benar-benar mengerti maksudnya dengan tidak menggunakan pendekatan reguler dalam kasus itu.


Bisakah menggunakan Action<>hasil kebocoran memori? Satu kelemahan dengan EventHandlerpola desain adalah kebocoran memori. Juga harus ditunjukkan bahwa mungkin ada beberapa Penangan Acara tetapi hanya satu Tindakan
Luke T O'Brien

4
@ LukeTO'Brien: Acara pada dasarnya adalah delegasi, sehingga kemungkinan kebocoran memori yang sama ada Action<T>. Juga, Action<T> dapat merujuk pada beberapa metode. Berikut adalah intisari yang menunjukkan bahwa: gist.github.com/fmork/4a4ddf687fa8398d19ddb2df96f0b434
Fredrik Mörk

88

Berdasarkan beberapa jawaban sebelumnya, saya akan memecah jawaban saya menjadi tiga area.

Pertama, keterbatasan fisik menggunakan Action<T1, T2, T2... >vs menggunakan kelas turunan dari EventArgs. Ada tiga: Pertama, jika Anda mengubah jumlah atau jenis parameter, setiap metode yang berlangganan harus diubah agar sesuai dengan pola baru. Jika ini adalah acara yang dihadapkan publik yang akan digunakan oleh majelis pihak ke-3, dan ada kemungkinan bahwa acara tersebut akan berubah, ini akan menjadi alasan untuk menggunakan kelas khusus yang berasal dari acara args demi konsistensi (ingat, Anda masih BISA masih gunakan a Action<MyCustomClass>) Kedua, menggunakan Action<T1, T2, T2... >akan mencegah Anda mengirimkan umpan balik KEMBALI ke metode pemanggilan kecuali Anda memiliki beberapa jenis objek (dengan properti Handled misalnya) yang diteruskan bersama dengan Aksi. Ketiga, Anda tidak mendapatkan parameter bernama, jadi jika Anda melewati boolangka 3 int, duastringdan DateTime, Anda tidak tahu apa arti dari nilai-nilai itu. Sebagai catatan tambahan, Anda masih dapat memiliki "Fire event ini dengan aman saat masih menggunakan Action<T1, T2, T2... >".

Kedua, implikasi konsistensi. Jika Anda memiliki sistem besar yang sudah Anda gunakan, hampir selalu lebih baik untuk mengikuti cara seluruh sistem dirancang kecuali Anda memiliki alasan yang sangat bagus untuk tidak melakukannya. Jika Anda secara terbuka menghadapi peristiwa yang perlu dipertahankan, kemampuan untuk mengganti kelas turunan bisa menjadi penting. Ingatlah itu.

Ketiga, praktik kehidupan nyata, saya pribadi menemukan bahwa saya cenderung membuat banyak peristiwa untuk hal-hal seperti perubahan properti yang perlu saya berinteraksi (Terutama ketika melakukan MVVM dengan model tampilan yang berinteraksi satu sama lain) atau di mana acara tersebut memiliki satu parameter. Sebagian besar waktu acara ini berbentuk public event Action<[classtype], bool> [PropertyName]Changed;atau public event Action SomethingHappened;. Dalam kasus ini, ada dua manfaat. Pertama, saya mendapatkan tipe untuk kelas penerbit. Jika MyClassmendeklarasikan dan merupakan satu-satunya kelas yang menembakkan event, saya mendapatkan instance eksplisit MyClassuntuk bekerja dengan dalam event handler. Kedua, untuk acara sederhana seperti acara perubahan properti, makna parameternya jelas dan dinyatakan atas nama event handler dan saya tidak harus membuat banyak kelas untuk acara semacam ini.


Posting blog yang luar biasa. Pasti layak dibaca jika Anda membaca utas ini!
Vexir

1
Jawaban terperinci dan dipikirkan dengan
matang

18

Pada sebagian besar, saya akan mengatakan ikuti polanya. Saya telah menyimpang dari itu, tetapi sangat jarang, dan untuk alasan tertentu. Dalam hal ini, masalah terbesar yang saya miliki adalah bahwa saya mungkin masih akan menggunakan Action<SomeObjectType>, memungkinkan saya untuk menambahkan properti tambahan nanti, dan untuk menggunakan properti 2 arah sesekali (pikirkan Handled, atau umpan balik-peristiwa lainnya di mana pelanggan perlu mengatur properti pada objek acara). Dan begitu Anda sudah memulai garis itu, Anda mungkin juga menggunakannya EventHandler<T>untuk beberapa T.


14

Keuntungan dari pendekatan wordier datang ketika kode Anda berada di dalam proyek 300.000 baris.

Menggunakan tindakan, seperti yang Anda miliki, tidak ada cara untuk memberi tahu saya apa bool, int, dan Blah. Jika tindakan Anda melewati objek yang menetapkan parameter, maka ok.

Menggunakan EventHandler yang menginginkan EventArgs dan jika Anda akan melengkapi contoh DiagnosticsArgs Anda dengan getter untuk properti yang mengomentari tujuannya maka aplikasi Anda akan lebih mudah dimengerti. Juga, beri komentar atau beri nama lengkap argumen dalam konstruktor DiagnosticsArgs.


6

Jika Anda mengikuti pola acara standar, maka Anda dapat menambahkan metode ekstensi untuk membuat pemeriksaan acara lebih aman / lebih mudah. (yaitu kode berikut menambahkan metode ekstensi yang disebut SafeFire () yang melakukan pemeriksaan nol, serta (jelas) menyalin acara ke dalam variabel terpisah agar aman dari kondisi ras nol biasa yang dapat mempengaruhi acara.)

(Meskipun saya dalam dua jenis pikiran apakah Anda harus menggunakan metode ekstensi pada objek nol ...)

public static class EventFirer
{
    public static void SafeFire<TEventArgs>(this EventHandler<TEventArgs> theEvent, object obj, TEventArgs theEventArgs)
        where TEventArgs : EventArgs
    {
        if (theEvent != null)
            theEvent(obj, theEventArgs);
    }
}

class MyEventArgs : EventArgs
{
    // Blah, blah, blah...
}

class UseSafeEventFirer
{
    event EventHandler<MyEventArgs> MyEvent;

    void DemoSafeFire()
    {
        MyEvent.SafeFire(this, new MyEventArgs());
    }

    static void Main(string[] args)
    {
        var x = new UseSafeEventFirer();

        Console.WriteLine("Null:");
        x.DemoSafeFire();

        Console.WriteLine();

        x.MyEvent += delegate { Console.WriteLine("Hello, World!"); };
        Console.WriteLine("Not null:");
        x.DemoSafeFire();
    }
}

4
... tidak bisakah kamu melakukan hal yang sama dengan Action <T>? SafeFire <T> (Tindakan ini <T> theEvent, T theEventArgs) harus berfungsi untuk ... dan tidak perlu menggunakan "di mana"
Beachwalker

6

Saya menyadari bahwa pertanyaan ini sudah berusia lebih dari 10 tahun, tetapi bagi saya tampaknya bukan saja jawaban yang paling jelas belum ditanggapi, tetapi mungkin juga tidak terlalu jelas dari pertanyaan, pemahaman yang baik tentang apa yang terjadi di balik selimut. Selain itu, ada pertanyaan lain tentang ikatan yang terlambat dan apa artinya sehubungan dengan delegasi dan lambda (lebih lanjut tentang itu nanti).

Pertama untuk mengatasi 800 lb gajah / gorila di dalam ruangan, kapan harus memilih eventvs Action<T>/ Func<T>:

  • Gunakan lambda untuk menjalankan satu pernyataan atau metode. Gunakan eventketika Anda menginginkan lebih dari model pub / sub dengan banyak pernyataan / lambdas / fungsi yang akan dieksekusi (ini adalah perbedaan utama langsung dari kelelawar).
  • Gunakan lambda ketika Anda ingin mengkompilasi pernyataan / fungsi untuk ekspresi pohon. Gunakan delegasi / acara ketika Anda ingin berpartisipasi dalam ikatan yang lebih tradisional seperti yang digunakan dalam refleksi dan COM interop.

Sebagai contoh dari suatu acara, mari memasang serangkaian acara sederhana dan 'standar' menggunakan aplikasi konsol kecil sebagai berikut:

public delegate void FireEvent(int num);

public delegate void FireNiceEvent(object sender, SomeStandardArgs args);

public class SomeStandardArgs : EventArgs
{
    public SomeStandardArgs(string id)
    {
        ID = id;
    }

    public string ID { get; set; }
}

class Program
{
    public static event FireEvent OnFireEvent;

    public static event FireNiceEvent OnFireNiceEvent;


    static void Main(string[] args)
    {
        OnFireEvent += SomeSimpleEvent1;
        OnFireEvent += SomeSimpleEvent2;

        OnFireNiceEvent += SomeStandardEvent1;
        OnFireNiceEvent += SomeStandardEvent2;


        Console.WriteLine("Firing events.....");
        OnFireEvent?.Invoke(3);
        OnFireNiceEvent?.Invoke(null, new SomeStandardArgs("Fred"));

        //Console.WriteLine($"{HeightSensorTypes.Keyence_IL030}:{(int)HeightSensorTypes.Keyence_IL030}");
        Console.ReadLine();
    }

    private static void SomeSimpleEvent1(int num)
    {
        Console.WriteLine($"{nameof(SomeSimpleEvent1)}:{num}");
    }
    private static void SomeSimpleEvent2(int num)
    {
        Console.WriteLine($"{nameof(SomeSimpleEvent2)}:{num}");
    }

    private static void SomeStandardEvent1(object sender, SomeStandardArgs args)
    {

        Console.WriteLine($"{nameof(SomeStandardEvent1)}:{args.ID}");
    }
    private static void SomeStandardEvent2(object sender, SomeStandardArgs args)
    {
        Console.WriteLine($"{nameof(SomeStandardEvent2)}:{args.ID}");
    }
}

Output akan terlihat sebagai berikut:

masukkan deskripsi gambar di sini

Jika Anda melakukan hal yang sama dengan Action<int>atau Action<object, SomeStandardArgs>, Anda hanya akan melihat SomeSimpleEvent2dan SomeStandardEvent2.

Jadi apa yang terjadi di dalam event?

Jika kami memperluas FireNiceEvent, kompiler sebenarnya menghasilkan yang berikut (Saya telah menghilangkan beberapa detail sehubungan dengan sinkronisasi utas yang tidak relevan dengan diskusi ini):

   private EventHandler<SomeStandardArgs> _OnFireNiceEvent;

    public void add_OnFireNiceEvent(EventHandler<SomeStandardArgs> handler)
    {
        Delegate.Combine(_OnFireNiceEvent, handler);
    }

    public void remove_OnFireNiceEvent(EventHandler<SomeStandardArgs> handler)
    {
        Delegate.Remove(_OnFireNiceEvent, handler);
    }

    public event EventHandler<SomeStandardArgs> OnFireNiceEvent
    {
        add
        {
            add_OnFireNiceEvent(value)
        }
        remove
        {
            remove_OnFireNiceEvent(value)

        }
    }

Compiler menghasilkan variabel delegasi pribadi yang tidak terlihat oleh namespace kelas di mana ia dihasilkan. Delegasi itu adalah apa yang digunakan untuk manajemen langganan dan partisipasi yang terlambat mengikat, dan antarmuka yang menghadap publik adalah yang akrab +=dan -=operator yang kita semua kenal dan sukai:)

Anda dapat mengubahsuaikan kode untuk penambah / hapus penangan dengan mengubah cakupan FireNiceEventdelegasi menjadi terlindungi. Ini sekarang memungkinkan pengembang untuk menambahkan kait khusus ke kait, seperti penebangan atau kait keamanan. Ini benar-benar membuat untuk beberapa fitur yang sangat kuat yang sekarang memungkinkan aksesibilitas khusus untuk berlangganan berdasarkan peran pengguna, dll. (Sebenarnya Anda bisa dengan mengkompilasi pohon ekspresi kustom, tapi itu di luar cakupan respons ini).

Untuk mengatasi beberapa poin dari beberapa respons di sini:

  • Tidak ada perbedaan dalam 'kerapuhan' antara mengubah daftar args Action<T>dan mengubah properti dalam kelas yang diturunkan EventArgs. Entah tidak hanya akan memerlukan perubahan kompilasi, mereka berdua akan mengubah antarmuka publik dan akan memerlukan versi. Tidak ada perbedaan.

  • Sehubungan dengan yang merupakan standar industri, itu tergantung pada di mana ini digunakan dan mengapa. Action<T>dan seperti itu sering digunakan dalam IoC dan DI, dan eventsering digunakan dalam perutean pesan seperti kerangka kerja tipe GUI dan MQ. Perhatikan bahwa saya sering berkata , tidak selalu .

  • Delegasi memiliki umur yang berbeda dari lambda. Kita juga harus menyadari penangkapan ... tidak hanya dengan penutupan, tetapi juga dengan gagasan 'lihat apa yang diseret kucing'. Ini tidak memengaruhi jejak memori / masa pakai serta manajemen alias kebocoran.

Satu hal lagi, sesuatu yang saya rujuk sebelumnya ... gagasan tentang ikatan yang terlambat. Anda akan sering melihat ini ketika menggunakan kerangka kerja seperti LINQ, mengenai kapan lambda menjadi 'hidup'. Itu sangat berbeda dari pengikatan seorang delegasi yang terlambat, yang dapat terjadi lebih dari satu kali (yaitu lambda selalu ada di sana, tetapi pengikatan terjadi atas permintaan sesering yang diperlukan), berbeda dengan sebuah lambda, yang begitu terjadi, hal itu dilakukan - Keajaiban hilang, dan metode (properti) / properti akan selalu mengikat. Sesuatu yang perlu diingat.


4

Melihat pola acara .NET standar yang kami temukan

Tanda tangan standar untuk delegasi acara .NET adalah:

void OnEventRaised(object sender, EventArgs args);

[...]

Daftar argumen berisi dua argumen: pengirim , dan argumen acara. Jenis waktu kompilasi pengirim adalah System.Object, meskipun Anda mungkin tahu jenis yang lebih diturunkan yang selalu benar. Dengan konvensi, gunakan objek .

Di bawah ini pada halaman yang sama kami menemukan contoh definisi acara tipikal yang mirip

public event EventHandler<EventArgs> EventName;

Seandainya kita mendefinisikan

class MyClass
{
  public event Action<MyClass, EventArgs> EventName;
}

pawang bisa saja

void OnEventRaised(MyClass sender, EventArgs args);

di mana sendermemiliki tipe yang benar ( lebih diturunkan ).


Maaf tidak berkomentar bahwa perbedaannya ada pada tanda tangan penangan, yang akan mendapat manfaat dari pengetikan yang lebih tepat sender .
user1832484
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.