Events vs. Delegates

Zdarzenia oraz delegaty (tak to chyba się tłumaczy?) pełnią podobną funkcje w C#. Jaka jest jednak różnica? Przyjrzyjmy się sposobowi ich użycia:

class Program
{
    public static event EventHandler SimpleEvent = null;
    public static EventHandler SimpleDelegate = null;

    static void Main(string[] args)
    {
        SimpleEvent+=new EventHandler(EventMethod);
        SimpleDelegate += new EventHandler(DelegateMethod);
        
        SimpleEvent(null, null);
        SimpleDelegate(null, null);
    }
    static void DelegateMethod(object sender, EventArgs e)
    {
    }
    static void EventMethod(object sender, EventArgs e)
    {
    }
}

Deklaracja różni się wyłącznie słowem kluczowym. Użycie jest identyczne. Istnieje jednak kilka różnic pomiędzy tymi mechanizmami:

  • Deklaracja w interfejsie

Zdarzenia (tak jak właściwości i metody) mogą być zadeklarowane w interfejsie i potem wymuszone w klasie. W przypadku czystych delagate jest to niemożliwe (interfejs może zawierać wyłącznie właściwości, zdarzenia, metody). Poniższa konstrukcja jest wiec jak najbardziej poprawna:

interface IExample
{
  event EventHandler SampleEvent; 
}

Z kolei poniższa deklaracja nie zostanie skompilowana:

interface IExample
{
  EventHandler SampleEvent; 
}

  • Wywołania

Zdarzenia mogą być wyłącznie wywoływane w klasie, w której zostały zadeklarowane. Niemożliwe jest odpalenie zdarzenia z klasy pochodnej np:

class BaseClass
{
    public event EventHandler SampleEvent=null;
}
class SubClass:BaseClass
{
    private void SomeMethod()
    {
        if(SampleEvent!=null)
            SampleEvent(null,null); // błąd!
    }        
}

W przypadku delegat powyższa konstrukcja jest poprawna.

  • Słowa kluczowe Add \ Remove

Zdarzenia dostarczają dwa nowe słowa kluczowe Add oraz Remove. Są to odpowiedniki dla Get\Set, znanych z właściwości. Przykład (źródło: MSDN):

public class PropertyEventsSample 
{
   private Hashtable eventTable = new Hashtable();

   public event MyDelegate1 Event1 
   {
      add 
      {
         eventTable["Event1"] = (MyDelegate1)eventTable["Event1"] + value;
      }
      remove
      {
         eventTable["Event1"] = (MyDelegate1)eventTable["Event1"] - value; 
      }
   }

   public event MyDelegate1 Event2 
   {
      add 
      {
         eventTable["Event2"] = (MyDelegate1)eventTable["Event2"] + value;
      }
      remove
      {
         eventTable["Event2"] = (MyDelegate1)eventTable["Event2"] - value; 
      }
   }

   public event MyDelegate2 Event3 
   {
      add 
      {
         eventTable["Event3"] = (MyDelegate2)eventTable["Event3"] + value;
      }
      remove
      {
         eventTable["Event3"] = (MyDelegate2)eventTable["Event3"] - value; 
      }
   }

   public event MyDelegate3 Event4 
   {
      add 
      {
         eventTable["Event4"] = (MyDelegate3)eventTable["Event4"] + value;
      }
      remove
      {
         eventTable["Event4"] = (MyDelegate3)eventTable["Event4"] - value; 
      }
   }

   public event MyDelegate3 Event5 
   {
      add 
      {
         eventTable["Event5"] = (MyDelegate3)eventTable["Event5"] + value;
      }
      remove
      {
         eventTable["Event5"] = (MyDelegate3)eventTable["Event5"] - value; 
      }
   }

   public event MyDelegate4 Event6 
   {
      add 
      {
         eventTable["Event6"] = (MyDelegate4)eventTable["Event6"] + value;
      }
      remove
      {
         eventTable["Event6"] = (MyDelegate4)eventTable["Event6"] - value; 
      }
   }
}

  • Sygnatura

Oczywiście zdarzenia mogą mieć dowolną sygnaturę, ale przyjęło się, że powinna wyglądać następująco:

void Name(object sender, EventArgs args)

Argumenty to dowolna klasa dziedzicząca po EventArgs.

5 thoughts on “Events vs. Delegates”

  1. Odnoście “Deklaracja w interfejsie”, dla ścisłości nie jest to problem delagate, a tego że w interface NIE mogą się znajdować pola, zatem deklaracja delagate JEST możliwa (np. za pomocą właściwości, a nie za pomocą pola)

  2. Hej, delegata można jak najbardziej użyć w interfejsie, ale musi być on zdefiniowany jako właściwość. Tak jak napisałeś, interface nie może zawierać definicji pól. Skądinąd ciekawy post:) pozdrawiam

  3. Faktycznie, trochę nieprecyzyjnie się wyraziłem. Chodziło oczywiście o taka samą deklaracje jak w przypadku event i pokazane różnicy między nimi:)
    Pozdrawiam
    Piotr

  4. Jeśli mnie pamięć nie myli, jest też jakaś różnica na korzyść delegatów w przypadku aplikacji wielowątkowej. Niestety nie odgrzebię teraz szczegółów.

  5. Zabrakło mi tu wyraźniejszego wytłumaczenia, czym jest zdarzenie: to prywatna delegata opakowana w publiczne (w zasadzie wszystko jedno, jakie) akcesory add i remove. Kod
    public event EventHandler MyEvent
    Kompiluje się identycznie jak:
    private EventHandler _myEvent;
    public event EventHandler MyEvent
    {
    [MethodImpl(MethodImplOptions.Synchronized)]
    add
    {
    _myEvent = (EventHandler)Delegate.Combine(_myEvent, value);
    }
    [MethodImpl(MethodImplOptions.Synchronized)]
    remove
    {
    _myEvent = (EventHandler)Delegate.Remove(_myEvent, value);
    }
    }

    @twk – jak widać powyżej – akcesory są Synchronized, czyli wykonują się w sekcji krytycznej. Ale to raczej różnica na korzyść zdarzeń, a nie gołej delegaty.

Leave a Reply

Your email address will not be published.