2009-07-12 28 views
6

uygulamanın en iyi yolu nedir? Aşağıdaki senaryo var. İstemci kodu, yalnızca Foo örneklerine doğrudan değil, FooHandler'a erişebilir.Zincirleme olayları C#

public delegate void FooLoaded(object sender, EventArgs e); 

class Foo { 
    public event FooLoaded loaded; 
    /* ... some code ... */ 
    public void Load() { load_asynchronously(); } 
    public void callMeWhenLoadingIsDone() { loaded(this,EventArgs.Empty); } 
} 

class FooHandler { 
    public event FooLoaded OneFooLoaded; 

    /* ... some code ... */ 

    public void LoadAllFoos() { 
     foreach (Foo f in FooList) { 
      f.loaded += new FooLoaded(foo_loaded); 
      f.Load(); 
     } 
    } 

    void foo_loaded(object sender, EventArgs e) { 
     OneFooLoaded(this, e); 
    } 

} 

Sonra istemciler foos yüklenmesi bildirimlerini almak için FooHandler sınıfının OneFooLoaded olayını kullanmak. Bu 'etkinlik zinciri' yapmak için doğru olan şey mi? Alternatif var mı? Bundan hoşlanmıyorum (yanlış hissettiriyor, nedenini tam olarak ifade edemiyorum), ama eğer işleyicinin erişim noktası olmasını istersem, pek çok alternatifim var gibi görünmüyor.

cevap

4

Sorunlar, iç iletişiminiz için gerekli olandan daha karmaşık ve dışa dönük olduğundan (eğer en azından kısmen doğru olduğuna inanıyorum) olayların birden fazla istemciyi arayabileceğini düşünürken, yanlış hissettiriyorsa, yalnızca bir bildirmeniz gerektiğini biliyorsunuz, değil mi?), o zaman aşağıdaki alternatifi öneririm. Foo'nun FooHandler ile tamamlanmasını bildirmek için olayları kullanmak yerine, Foo zaten dahili olduğu için, Foo'nun yüklenmesi tamamlandığında Foo'nun çağrılabileceği kurucuya veya Geri yükleme yöntemine bir geri arama parametresi ekleyebilirsiniz. Bu parametre sadece bir geri dönüşünüz varsa, ya da çok sayıda varsa bir arabirim olabilir, sadece bir işlev olabilir. Bu da daha güçlü FooLoaded temsilci ile yazılmalıdır olanak

public delegate void FooLoaded(FooHandler sender, EventArgs e); 

class Foo 
{ 
    Action<Foo> callback; 
    /* ... some code ... */ 
    public void Load(Action<Foo> callback) { this.callback = callback; load_asynchronously(); } 
    public void callMeWhenLoadingIsDone() { callback(this); } 
} 

class FooHandler 
{ 
    public event FooLoaded OneFooLoaded; 

    /* ... some code ... */ 

    public void LoadAllFoos() 
    { 
    foreach (Foo f in FooList) 
    { 
     f.Load(foo_loaded); 
    } 
    } 

    void foo_loaded(Foo foo) 
    { 
    // Create EventArgs based on values from foo if necessary 
    OneFooLoaded(this, null); 
    } 

} 

Bildirim o: Ben senin kod basitleştirilmiş iç arayüzü ile görüneceğini düşünüyorum şekli aşağıdaki gibidir.

Öte yandan, bu olayın kendisini yanlış hissetmesi durumunda, olayın müşteriye ulaşmak için FooHandler'den geçmesi gerekmemesi gerekir. 1) Eğer müşterinin anlaşmazlığa düşmesi durumunda bireysel Foo nesneleri, bu seviyedeki onlardan olayları batırmamalı ve 2) Eğer bunu gerçekten yapmak istiyorsan, Foo'nun özel olmasına rağmen Foo'da bazı genel geri çağırma arayüzlerini uygulayabilirsin ya da Pavel gibi bir mekanizmayı kullanabilirsin. önerdi. Bununla birlikte, müşterilerin daha az sayıda olay işleyicisini uygulamaya koyma ve kaynağı, onlarca küçük nesneden gelen olayları birbirine bağlamak (ve potansiyel olarak kesmek) yerine bir işleyicide ayırt etmenin basitliğini beğeniyorum.

+0

Şu anda üzerinde çalışıyorum projeye başlamadan önce bunu görmek isterdim, bu benim için işleri çok daha kolay hale getirdi (ve birim testine çok daha kolay). – ForbesLindesay

1

Bu tür olay şelalesinin olay şelalesinin birkaç kez oldukça doğal bir şekilde geldiğim bir şey olduğunu söyleyebilirim ve henüz onlarla ciddi bir sorunla karşılaşıyorum.

Hiç olayları şeffaf bir şekilde geçirdiğimi düşünmüyorum, ama her zaman anlamsal bir değişim ile. Örneğin FooLoaded, AllFoosLoaded olur. Bunun gibi bir anlamsal değişikliği değiştirmek istiyorsanız, OneFooLoaded'u bir yüzde göstergesine dönüştürebilirsiniz (örneğin, alıcı sınıfın kaç tane Foo s yüklendiğini bilmesi gerekir).

Bu gibi yapıların yanlış olduğunu düşünüyorum çünkü bir event yayını içindir. Dersi yayınlayan bir sözleşmeyi gerçekten de taahhüt etmez, aynı zamanda aboneye taahhütte bulunmaz. Bununla birlikte, dış cephe sınıfları ve genel bilgi gizleme ilkeleri, sözleşmelerin uygulanmasını kolaylaştırmak için tasarlanmıştır.

Halen konuyla ilgili düşüncelerimi topluyorum, yukarıdakiler biraz belirsizse üzgünüm, ama istediğinizi yapmak için daha iyi bir yol olup olmadığını bilmiyorum. Ve eğer varsa, seninle olduğu gibi görmek istiyorum.

1

yerine ortaya çıkmasına sebep oluyor, olayı add ve remove temsilci olabilir:

class FooHandler { 
    public event FooLoaded OneFooLoaded { 
     add { 
      foreach (Foo f in FooList) { 
       f.loaded += new FooLoaded(value); 
      } 
     } 
     remove { 
      foreach (Foo f in FooList) { 
       f.loaded -= new FooLoaded(value); 
      } 
     } 
    } 

    public void LoadAllFoos() { 
     foreach (Foo f in FooList) { 
      f.Load(); 
     } 
    } 
} 

yukarıda FooListFooHandler ömrü boyunca değişmez olduğunu varsayar. Değiştirilebilirse, öğelerin eklenmesi/silinmesini de izlemek zorundasınız ve işleyicileri buna göre ekleyin/kaldırın.

3

Bunu yapmanın farklı bir yolu, tüm olayların geçtiği etki alanında tek bir nokta (tek bir sınıf) oluşturmaktır. Etki alanını kullanan tüm sınıflar, statik olayların bir listesine ve bu sınıf tarafından herhangi bir dahili sınıf etkinliğine sahip olan bu sınıfa bağlanacak ve böylece alandaki olay zincirlemesinden en az kaçınılacaktır.

Referanslar: veya yararlı olabilir veya olmayabilir ipuçları

+0

Ben sadece bunu buldum ve fantastik, 2 ya da 3 etkinlik kurmak ve spagetti almak için çok daha basit olduğunu düşünüyorum – Calanus

2

Çift ...

olay bildirimi aşağıdaki gibi yaz:

public event FooLoaded loaded = delegate {}; 

Bu şekilde, hiç müşteri listelenmemiş olsa bile güvenle tetikleyebilirsiniz.zincirleme olaylar konuda

, iki etkinlik olduğunda:

public event EventHandler a = delegate {}; 
public event EventHandler b = delegate {}; 

Ayrıca ateşlemesini neden b ateşlemesini isteyebilirsiniz:

b += (s, e) => a(s, e); 

Ve sonra belki bu bakıp şöyle demek daha özlü olacağını düşünüyorum: Resharper bile önerebilir, Nitekim

b += a; 

sana! Ama tamamen farklı bir şey ifade ediyor. Bu akım içindekilerin b için a arasında ekler, böylece daha sonra daha işleyicileri a ile askere, bu b ateşlendiğinde onları çağrılacak neden olmaz.