2009-04-07 18 views
52

Uczę się o zdarzeniach/delegatach w języku C#. Czy mógłbym zapytać o twoją opinię na temat wybranego przeze mnie stylu/stylu kodowania (zaczerpniętego z książki Head First C#)?Zdarzenia - nazewnictwo konwencji i stylu

Nauczam o tym przyjaciela o tym jutro i staram się wymyślić najbardziej elegancki sposób wyjaśnienia pojęć. (Myślałem najlepszym sposobem na zrozumienie tematu jest, aby spróbować i nauczyć go!)

class Program 
    { 
     static void Main() 
     { 
      // setup the metronome and make sure the EventHandler delegate is ready 
      Metronome metronome = new Metronome(); 

      // wires up the metronome_Tick method to the EventHandler delegate 
      Listener listener = new Listener(metronome); 
      metronome.OnTick(); 
     } 
    } 

public class Metronome 
    { 
     // a delegate 
     // so every time Tick is called, the runtime calls another method 
     // in this case Listener.metronome_Tick 
     public event EventHandler Tick; 

     public void OnTick() 
     { 
      while (true) 
      { 
       Thread.Sleep(2000); 
       // because using EventHandler delegate, need to include the sending object and eventargs 
       // although we are not using them 
       Tick(this, EventArgs.Empty); 
      } 
     } 
    } 

public class Listener 
    { 
     public Listener(Metronome metronome) 
     { 
      metronome.Tick += new EventHandler(metronome_Tick); 
     } 

     private void metronome_Tick(object sender, EventArgs e) 
     { 
      Console.WriteLine("Heard it"); 
     } 
    } 

nb Kod jest refaktoryzowany z http://www.codeproject.com/KB/cs/simplesteventexample.aspx

Odpowiedz

46

Istnieje kilka punktów, które chciałbym wspomnieć:

Metronome.OnTick nie wydaje się być nazwane poprawnie. Semantycznie "OnTick" mówi mi, że zostanie wywołany, gdy "Tick", ale tak naprawdę nie jest tak, co się dzieje. Nazwałbym to zamiast tego "Go".

Typowo akceptowanym modelem byłoby jednak wykonanie następujących czynności. OnTick to wirtualna metoda, która podnosi wydarzenie. W ten sposób możesz łatwo przesłonić domyślne zachowanie w klasach odziedziczonych i wywołać bazę, aby podnieść wydarzenie.

class Metronome 
{ 
    public event EventHandler Tick; 

    protected virtual void OnTick(EventArgs e) 
    { 
     //Raise the Tick event (see below for an explanation of this) 
     var tickEvent = Tick; 
     if(tickEvent != null) 
      tickEvent(this, e); 
    } 

    public void Go() 
    { 
     while(true) 
     { 
      Thread.Sleep(2000); 
      OnTick(EventArgs.Empty); //Raises the Tick event 
     } 
    } 
} 

Również wiem, że to prosty przykład, ale jeśli nie ma słuchaczy załączone, kod rzuci na Tick(this, EventArgs.Empty). powinien zawierać co najmniej null strażnika, by sprawdzić dla słuchaczy:

if(Tick != null) 
    Tick(this, EventArgs.Empty); 

Jest to jednak nadal narażone w środowisku wielowątkowym, jeśli słuchacz nie jest zarejestrowana pomiędzy osłoną i inwokacji. Najlepiej byłoby, aby uchwycić obecnych słuchaczy pierwszego i nazywają je:

var tickEvent = Tick; 
if(tickEvent != null) 
    tickEvent(this, EventArgs.Empty); 

wiem, że to jest stary odpowiedź, ale ponieważ wciąż zbierając upvotes, oto C# 6 sposobów robienia rzeczy. Cała „straż” koncepcja może być zastąpiony warunkowego wywołania metody i kompilator rzeczywiście robi Right Thing (TM) w odniesieniu do przechwytywania słuchaczy:

Tick?.Invoke(this, EventArgs.Empty); 
+12

Alternatywą dla wartownika jest dodanie "= delegate {};" deklaracji Tick (zobacz http://stackoverflow.com/questions/231525/raising-c-events-with-anextext-method-is-it-bad/231536#231536) – Benjol

+0

Należy pamiętać, że luka nie jest _ograniczona_ do środowiska wielowątkowe. Jest możliwe (jeśli socjopatyczne), że funkcja obsługi zdarzeń usunie wszystkie procedury obsługi ze zdarzenia, powodując awarię po zakończeniu obsługi, a wywołanie zdarzenia spowoduje uruchomienie następnego (obecnie nieistniejącego) zdarzenia. –

+0

@GregD: Czy jest jakiś sposób na to, żeby go odblokować, aby kod klienta nie mógł tego zrobić? –

2

Wygląda dobrze, pomijając fakt, że OnTick nie podąża za typowym modelem wywołania zdarzenia. Zazwyczaj On[EventName] podnosi zdarzenie jeden raz, jak

protected virtual void OnTick(EventArgs e) 
{ 
    if(Tick != null) Tick(this, e); 
} 

rozważyć utworzenie tej metody, a zmiana nazwy istniejącego „OnTick” metodę „StartTick”, a zamiast wywoływać Tick bezpośrednio od StartTick, zadzwoń OnTick(EventArgs.Empty) z StartTick metoda.

4

Punkt znalazłem po użyciu wydarzenia w. Sieć przez wiele lat to powtarzająca się konieczność sprawdzania zdarzenia dla obsługi zerowej przy każdym wywołaniu. Jeszcze nie widzę fragmentu kodu na żywo, który robi cokolwiek, ale nie wywołuje zdarzenia, jeśli ma wartość zerową.

To, co zacząłem robić, to umieszczanie obojętnego programu obsługi na każdym utworzonym przeze mnie wydarzeniu, aby zapisać konieczność wykonania zerowej kontroli.

public class Metronome 
{ 
    public event EventHandler Tick =+ (s,e) => {}; 

    protected virtual void OnTick(EventArgs e) 
    { 
     Tick(this, e); // now it's safe to call without the null check. 
    } 
} 
+1

Jedną z rzeczy, które należy podkreślić, jest to, że nie działa to w przypadku serializacji. Idea pustego delegata jest dokładnie omówiona na http://stackoverflow.com/questions/9033/hidden-features-of-c/9282#9282 –

+1

Może być jeszcze łatwiej: 'public event EventHandler Tick = delegate {};' – Mikhail

56

Firma Microsoft napisała obszerny zestaw wskazówek dotyczących nazewnictwa i umieściła go w bibliotece MSDN. Można znaleźć artykuły tutaj: Guidelines for Names

Oprócz wytycznych ogólnych kapitalizacji, o to co to ma do „Wydarzenia” na stronie Names of Type Members:

zrobienia Wydarzenia nazw z czasownikiem lub czasownik frazy .

Nazwy zdarzeń należy nadać koncepcji przed i po użyciu bieżącego i czasu przeszłego. Na przykład zamknięcie zdarzenia, które zostanie wywołane przed zamknięciem okna, zostanie nazwane Zamknięcie, a jeśli zostanie zamknięte po zamknięciu okna, będzie ono nazywane Zamknięte.

Nie używaj przedrostków Przedrostka ani Przedrostków do wskazywania zdarzeń przed i po .

Czy obsługują zdarzenia zdarzenia (delegaci używali jako typów zdarzeń) z sufiksem EventHandler .

Używaj dwóch parametrów o nazwie nadawca i e w podpisach obsługi zdarzeń.

Parametr nadajnik powinien być typu obiektów i parametrów E jest wystąpienie lub dziedziczyć EventArgs.

Wykonywanie klas argumentów zdarzeń z przyrostkiem EventArgs.

12

Powiedziałbym, że najlepszym przewodnikiem po wydarzeniach w ogóle, w tym konwencjach nazewnictwa, jest here.

To konwencja ja przyjęły, krótko:

  • Wydarzenia nazwy są zwykle zakończone czasownik kończąc -ing czy -ed (zamykanie/zamknięte, Załadunek/Loaded)
  • Klasa, która deklaruje, że zdarzenie powinno mieć chronioną wirtualną opcję [EventName], która powinna być używana przez resztę klasy do podnoszenia zdarzenia. Ta metoda może być również używana przez podklasy do podnoszenia zdarzenia, a także przeciążania w celu modyfikacji logiki zdarzeń.
  • Jest często zamieszanie o wykorzystaniu „Handler” - dla spójności, wszyscy delegaci powinni być utrwalano z Handler, starają się unikać wywoływania metod, które implementują handler „Wozy”
  • domyślnym VS nazewnictwo dla metody implementuje moduł obsługi EventPublisherName_EventName.
2

W twoim przypadku może to być:

class Metronome { 
    event Action Ticked; 

    internalMethod() { 
    // bla bla 
    Ticked(); 
    } 
} 

Przede użytku sampple poniżej konwencji, self-opisywania;]

Wydarzenia Źródło:

class Door { 

    // case1: property change, pattern: xxxChanged 
    public event Action<bool> LockStateChanged; 

    // case2: pure action, pattern: "past verb" 
    public event Action<bool> Opened; 

    internalMethodGeneratingEvents() { 
    // bla bla ... 

    Opened(true); 
    LockStateChanged(false); 
    } 

} 

BTW. Hasło event jest opcjonalne, ale umożliwia wyróżniające „Wydarzenia” z

Zdarzenia słuchacza „Callbacki”:

class AlarmManager { 

    // pattern: NotifyXxx 
    public NotifyLockStateChanged(bool state) { 
    // ... 
    } 

    // pattern: [as above]  
    public NotifyOpened(bool opened) { 
    // OR 
    public NotifyDoorOpened(bool opened) { 
    // ... 
    } 

} 

i wiążące [kod wygląda przyjazny człowiekowi]

door.LockStateChanged += alarmManager.NotifyLockStateChanged; 
door.Moved += alarmManager.NotifyDoorOpened; 

Nawet ręcznie wysyłania zdarzeń jest „ludzki czytelny".

alarmManager.NotifyDoorOpened(true); 

Czasami bardziej wyraziste może być "czasownik + ing"

dataGenerator.DataWaiting += dataGenerator.NotifyDataWaiting; 

Niezależnie konwencja wybrać, być z nim zgodne.

Powiązane problemy