C# události pro callback funkce

Události (event) v .NET framework jsou efektivní a velmi jednoduchý způsob, jak implementovat návrhový vzor observer. Klíčové slovo event zná asi každý, kdo s C# přišel do styku. Při práci na .NET wrapperu pro SQLite jsem ale narazil na zajímavý problém – jak zajistit, aby callback funkce vyvolávající událost byla zaregistrována, jen pokud na událost čeká alespoń jeden klient?

V duchu ostatních komponent v .NET framework by bylo očekavatelné, že by pro třídu reprezentující událost byly definované události AddDelegate a RemoveDelegate – tím bychom ale objekt definovali rekursivně tak, že každá jeho instance by obsahovala instanci objektu stejné třídy. Což pochopitelně může vést při implementaci k nekonečným problémům s nekonečnými rekursivními strukturami.

Jak se překládají události

class PozarniAlarm {
  public event EventHandler Hori;
}

Při pohledu na kód vygenerovaný překladačem pro událost je vidět, že pro událost je překladačem vygenerovaný delegát se stejnou signaturou, jakou má událost a se stejným jménem jako má událost; dále pro ni jsou vygenerovány metody add_Hori(EventHandler) a remove_Hori(EventHandler).

Pokud v kódu pracujeme s událostí pomocí jejího názvu, až na operátory += a -= pracujeme právě s tímto delegátem. Zmíněné operátory se pochopitelně přeloží na volání odpovídající metody add_Hori, nebo remove_Hori.
Události, které by zachycovaly přidání nebo odebrání klienta události neexistují, ale nabízí se otázka, jestli by nebylo možné nějakým způsobem přetížit ony metody pro += a -=. A jak zní odpověď? Ale samozřejmě…

Vlastní add a remove

Podobně jako se u properties zadávají bloky kódu pro přečtení a zápis hodnoty, zadávají se i bloky kódu pro přidání a odebrání handleru, celková struktura je properties velmi podobná.

class PozarniAlarm {
  public event EventHandler Hori {
    add {
      // ... kód pro přidání obsluhy ...
    }
    remove {
      // ... kód pro odebrání obsluhy ...
    }
  }
}

Pokud části add a remove neuvedete, překladač automaticky za vás vygeneruje kód analogický tomuto pro přidávání

Hori = (EventHandler)EventHandler.Combine(Hori, value);

a tomuto pro odebírání

Hori = (EventHandler)EventHandler.Remove(Hori, value);

Hori v těchto blocích kódu samozřejmě vystupuje ve významu delegáta, ve kterém se akumulují všechny delegáty přidané k události pomocí operátoru +=.

Pokud naopak bloky add a remove zadáme, překladač automaticky negeneruje proměnnou akumulující přidané delegáty a je nutné se o ni postarat. A tato proměnná, kterou pro událost vytvoříme ručně bohužel nesmí mít stejné jméno jako událost. Škoda…

Spojení s callback funkcí

V tomto bodě už je jednoduché do procesu vstoupit a ve vhodnou chvíli zaregistrovat svou callback funkci, která událost vyvolává.

class PozarniAlarm {
  public event EventHandler Hori {
    add {
      // zjednodušená syntaxe pro spojování delegátů
      m_HoriListeners += value;
      // pokud se k události přihlásil první čekatel, zaregistruj
      // callback funkci
      if(m_HoriListeners.GetInvocationList().Length == 1)
        RegisterCallback();
    }
    remove {
      // pokud se odhlašuje poslední čekatel, odregistruj
      callback funkci
      if(m_HoriListeners.GetInvocationList().Length == 1)
        UnregisterCallback();
      m_HoriListeners -= value;
    }
  }
  // pro události, kde jsou zadané add a remove se proměnná
  // udržující seznam přidaných delegátů nevygeneruje
  // automaticky, musíme si jí vyrobit sami
  private EventHandler m_HoriListeners;

  private void RegisterCallback() {
    // ... proveď registraci callback funkce vyvolávající
    //     událost Hori ...
  }

  private void UnregisterCallback() {
    // ... zruš registraci callback funkce vyvolávající
    //     událost Hori ...
  }
}
Bookmark and Share

Comments are closed.