olay Eylem <> vs olay EventHandler <>


144

Bildirme event Action<>ve arasında herhangi bir fark var mı event EventHandler<>?

Aslında hangi nesnenin bir olayı gündeme getirdiği önemli değildir.

Örneğin:

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

vs

public event EventHandler<DiagnosticsArgs> DiagnosticsEvent;

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

her iki durumda da kullanım neredeyse aynı olacaktır:

obj.DiagnosticsEvent += HandleDiagnosticsEvent;

event EventHandler<>Desen hakkında sevmediğim birkaç şey var :

  • EventArgs'tan türetilen ekstra tür bildirimi
  • Nesne kaynağının zorunlu geçişi - çoğu zaman kimse umursamaz

Daha fazla kod, belirgin bir avantaj olmadan korumak için daha fazla kod anlamına gelir.

Sonuç olarak, tercih ederim event Action<>

Ancak, yalnızca <> Eyleminde çok fazla tür bağımsız değişkeni varsa, ek bir sınıf gerekir.


2
plusOne (Ben sadece sistemi yendi) "kimse umursamıyor" için
hyankov

@plusOne: Aslında göndereni tanımam gerekiyor! Bir şeylerin olduğunu ve kimin yaptığını bilmek istediğinizi söyleyin. 'Nesne kaynağına' (yani gönderen) ihtiyacınız vardı.
Kamran Bigdely

gönderen etkinliğin yükünde bir özellik olabilir
Thanasis Ioannidis

Yanıtlar:


67

Ana fark, Action<>etkinliğinizi kullanırsanız , sistemdeki neredeyse başka herhangi bir olayın tasarım modelini takip etmeyecek olması, bir dezavantaj olarak değerlendireceğim.

Hakim tasarım örüntüsünün bir tarafı (eşliğin gücü dışında), EventArgsolayın imzasını değiştirmeden nesneyi yeni özelliklerle genişletebilmenizdir . Eğer kullandıysanız bu hala mümkün olurdu Action<SomeClassWithProperties>, ama bu durumda düzenli yaklaşımı kullanmamanın nedenini gerçekten görmüyorum.


Kullanarak Could Action<>bellek sızıntıları sonucu? EventHandlerTasarım deseninin bir dezavantajı bellek sızıntılarıdır. Ayrıca, birden fazla Olay İşleyicisi olabileceğine, ancak yalnızca bir Eylem olabileceğine dikkat çekilmelidir
Luke T O'Brien

4
@ LukeTO'Brien: Olaylar özünde delegelerdir, bu nedenle aynı bellek sızıntısı olasılıkları mevcuttur Action<T>. Ayrıca, bir Action<T> edebilir çeşitli yöntemler bakın. İşte bunu gösteren bir öz
Fredrik

89

Önceki cevaplardan bazılarına dayanarak, cevabımı üç alana ayıracağım.

Birincisi, Action<T1, T2, T2... >türetilmiş bir sınıfı kullanarak vs kullanmanın fiziksel sınırlamaları EventArgs. Üç tane var: İlk olarak, parametre sayısını veya türlerini değiştirirseniz, abone olunan her yöntemin yeni desene uyacak şekilde değiştirilmesi gerekir. Bu, 3. taraf meclislerinin kullanacağı halka açık bir olaysa ve olay argümanlarının değişme olasılığı varsa, bu tutarlılık uğruna olay argümanlarından türetilen özel bir sınıf kullanmak için bir sebep olacaktır (hatırlayın, hala KONUŞABİLİRSİNİZ) an Action<MyCustomClass>) ikinci olarak, Action<T1, T2, T2... >eylem, eylemle birlikte geçirilen bir tür nesneniz (örneğin, bir Handled özelliği ile) yoksa geri çağırma işlemini geri çağırma yöntemine çağırmanızı engelleyecektir. Üçüncüsü, adlandırılmış parametreler alamazsınız, bu yüzden 3'ü boolbir int, ikistring's ve a DateTime, bu değerlerin anlamının ne olduğu hakkında hiçbir fikriniz yok. Bir yan not olarak, hala "Bu olayı kullanırken güvenli bir şekilde tetikleme yöntemi" uygulayabilirsiniz Action<T1, T2, T2... >.

İkincisi, tutarlılık sonuçları. Halihazırda birlikte çalıştığınız büyük bir sisteminiz varsa, çok iyi bir nedeniniz olmadığı sürece sistemin geri kalanının tasarlanma şeklini takip etmek neredeyse her zaman daha iyidir. Eğer halka açık olarak sürdürülmesi gereken olaylarınız varsa, türetilmiş sınıfların yerine koyma yeteneği önemli olabilir. Bunu aklınızda bulundurun.

Üçüncüsü, gerçek yaşam pratiği, kişisel olarak, etkileşime girmem gereken özellik değişiklikleri gibi şeyler için (özellikle birbirleriyle etkileşen görünüm modelleri ile MVVM yaparken) veya etkinliğin olduğu yerlerde bir sürü etkinlik oluşturma eğiliminde olduğumu fark ediyorum. tek bir parametre. Çoğu zaman bu olaylar public event Action<[classtype], bool> [PropertyName]Changed;veya biçimini alır public event Action SomethingHappened;. Bu durumlarda iki fayda vardır. İlk olarak, veren sınıf için bir tür olsun. Eğer MyClassbeyan ve olay ateş sadece sınıftır, ben açık bir örneğini almak MyClassolay işleyicisi içinde çalışmak için. İkincisi, özellik değiştirme olayları gibi basit olaylarda, parametrelerin anlamı olay işleyicinin adında açıktır ve belirtilmiştir ve bu tür olaylar için sayısız sınıf oluşturmak zorunda değilim.


Harika blog yazısı. Bu konuyu okuyorsanız kesinlikle okumaya değer!
Vexir

1

18

Çoğunlukla, kalıbı takip et derim. Ben var ondan sapmış, ama çok nadiren ve belirli nedenlerle. Bu durumda, sahip olduğum en büyük sorun, muhtemelen Action<SomeObjectType>daha sonra ekstra özellikler eklememe ve ara sıra 2 yönlü özelliği (düşünme Handledveya diğer geribildirim olayları) abonenin olay nesnesinde bir özellik ayarlaması gerekir ). Ve bu çizgiyi başlattıktan sonra, EventHandler<T>bazıları için de kullanabilirsiniz T.


14

Daha kötü bir yaklaşımın avantajı, kodunuz 300.000 satırlık bir proje içinde olduğunda ortaya çıkar.

Eylemi kullandığınız gibi, bana bool, int ve Blah'ın ne olduğunu söylemenin bir yolu yok. Eyleminiz parametreleri tanımlayan bir nesneyi geçtiyse tamam.

EventArgs isteyen bir EventHandler kullanmak ve DiagnosticsArgs örneğinizi amaçlarını açıklayan özellikler için getters ile tamamlarsanız, uygulamanız daha anlaşılır olacaktır. Ayrıca, lütfen DiagnosticsArgs yapıcısındaki bağımsız değişkenleri yorumlayın veya tam olarak adlandırın.


6

Standart olay düzenini izlerseniz, olay tetiklemenin kontrolünü daha güvenli / kolay hale getirmek için bir genişletme yöntemi ekleyebilirsiniz. (yani aşağıdaki kod, null kontrolünü yapan SafeFire () adlı bir genişletme yöntemi ve olayları etkileyebilecek olağan null yarış koşulundan güvende olmak için olayı ayrı bir değişkene kopyalamanın yanı sıra (açık bir şekilde) ekler.)

(Boş nesnelerde uzatma yöntemleri kullanmanız gerekip gerekmediği konusunda iki tür fikrim olmasına rağmen ...)

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
... Action <T> ile aynı şeyi yapamaz mısınız? SafeFire <T> (bu Eylem <T> theEvent, T theEventArgs) çalışmalı ... ve "nerede" kullanmaya gerek yok
Beachwalker

6

Bu sorunun 10 yaşın üzerinde olduğunu anlıyorum, ancak bana sadece en açık cevabın ele alınmadığı gibi değil, belki de sorudan kapakların altında neler olup bittiğini iyi bir şekilde anlayabileceği anlaşılıyor. Buna ek olarak, geç bağlanma ve delegeler ve lambdalar için bunun ne anlama geldiği hakkında başka sorular da var (daha sonra daha fazla).

İlk odası, seçim için 800 lb fil / goril adrese eventvs Action<T>/ Func<T>:

  • Bir deyimi veya yöntemi yürütmek için lambda kullanın. eventYürütülecek birden fazla ifade / lambdas / fonksiyon içeren bir pub / alt modelden daha fazlasını istediğinizde kullanın (bu, yarasadan büyük bir farktır).
  • İfade ağaçlarına ifadeler / işlevler derlemek istediğinizde lambda kullanın. Yansıma ve COM birlikte çalışma gibi daha geleneksel geç bağlama katılmak istediğinizde delege / olay kullanın.

Bir olay örneği olarak, aşağıdaki gibi küçük bir konsol uygulaması kullanarak basit ve 'standart' bir olay kümesi oluşturalım:

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

Çıktı aşağıdaki gibi görünecektir:

resim açıklamasını buraya girin

Aynı şeyi Action<int>veya ile Action<object, SomeStandardArgs>de yaparsanız, yalnızca SomeSimpleEvent2ve öğesini görürsünüz SomeStandardEvent2.

Peki içinde neler oluyor event?

Genişletirsek FireNiceEvent, derleyici aslında aşağıdaki üretir (Bu tartışma ile ilgili olmayan iş parçacığı senkronizasyonu ile ilgili bazı ayrıntıları atladım):

   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)

        }
    }

Derleyici, oluşturulduğu sınıf ad alanına görünmeyen özel bir delege değişkeni oluşturur. Bu delege, abonelik yönetimi ve geç bağlayıcı katılım için kullanılan şeydir ve halka açık arayüz, hepimizin tanıdığı ve sevdiği tanıdık +=ve -=operatörlerdir :)

FireNiceEventTemsilci kapsamını korumalı olarak değiştirerek ekleme / kaldırma işleyicileri için kodu özelleştirebilirsiniz . Bu, geliştiricilerin kancalara günlük tutma veya güvenlik kancaları gibi özel kancalar eklemelerine olanak tanır. Bu gerçekten kullanıcı rollerine, vb. Dayalı aboneliğe özelleştirilmiş erişilebilirlik sağlayan bazı çok güçlü özellikler sağlar. Bunu lambdas ile yapabilir misiniz? (Aslında ifade ağaçlarını derleyerek yapabilirsiniz, ancak bu yanıtın kapsamı dışındadır).

Buradaki bazı yanıtlardan birkaç noktayı ele almak için:

  • 'Args listesinin Action<T>değiştirilmesi ile türetilmiş bir sınıfın özelliklerinin değiştirilmesi arasında' kırılganlık 'açısından gerçekten bir fark yoktur EventArgs. Her ikisi de sadece derleme değişikliği gerektirmez, her ikisi de genel bir arabirimi değiştirir ve sürümlendirme gerektirir. Fark yok.

  • Hangisinin bir endüstri standardı olduğu, bunun nerede ve neden kullanıldığına bağlıdır. Action<T>ve bu genellikle IoC ve DI'da kullanılır ve eventgenellikle GUI ve MQ tipi çerçeveler gibi mesaj yönlendirmede kullanılır. Her zaman değil, sık sık söylediğime dikkat edin .

  • Delegelerin yaşamları lambdalardan farklı. Kişi de yakalamanın farkında olmalı ... sadece kapanışla değil, aynı zamanda 'kedinin neye sürüklendiğine bak' kavramıyla da. Bu, bellek ayak izini / ömrünü ve aynı zamanda yönetim sızıntılarını da etkiler.

Bir şey daha, daha önce bahsettiğim bir şey ... geç bağlanma kavramı. Bunu bir lambda 'canlı' olduğunda LINQ gibi bir çerçeve kullanırken sıklıkla göreceksiniz. Bu, bir defadan fazla olabilen bir delegenin geç bağlanmasından çok farklıdır (yani lambda her zaman oradadır, ancak bir lambda'nın aksine, isteğe bağlı olarak talep üzerine sıklıkla gerçekleşir) - sihir gitti ve yöntem (ler) / özellik (ler) her zaman bağlanacaktır. Akılda tutulması gereken bir şey.


4

Baktığımızda Standart .NET olay desenleri buluruz

Bir .NET olay temsilcisi için standart imza:

void OnEventRaised(object sender, EventArgs args);

[...]

Bağımsız değişken listesi iki bağımsız değişken içerir: gönderen ve olay bağımsız değişkenleri. Her zaman doğru olacak daha türetilmiş bir tür biliyor olsanız bile, gönderenin derleme zamanı türü System.Object'tir . Kural olarak, nesne kullanın .

Aynı sayfada, aşağıdaki gibi tipik olay tanımının bir örneğini bulduk

public event EventHandler<EventArgs> EventName;

Tanımlamış olsaydık

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

işleyici olabilirdi

void OnEventRaised(MyClass sender, EventArgs args);

burada senderdoğru ( daha türetilmiş ) tip vardır.


Farkın, daha hassas bir şekilde yazılmış bir fayda sağlayacağı işleyici imzasında olduğunu belirtmediğim için üzgünüm sender.
user1832484
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.