C# esetén egy osztály definiálhat eseményeket (event), amelyek arra jók, hogy az osztály saját belsÅ‘ állapotának megváltozásakor értesÃteni tudjon más osztályokat. Ehhez a "megfigyelÅ‘" osztályoknak fel kell iratkozni a megfigyelt osztály eseményeire. Ezt a viselkedést MegfigyelÅ‘ programtervezési mintának nevezzük.1
Az események valójában speciális delegate tÃpusok, vagyis feliratkozni úgy tudunk rá, mint egy delegate-re. Van azonban pár különbség egy sima delegate és event között. Ezek:
- Esemény definiálható interfészben, mÃg
delegatenem. - Eseményt csak a deklaráló osztály tudja kiváltani, külsőleg nem előidézhető.
- Az esemény tulajdonság szerű. Rendelkezik
addésremovemetódusokkal, amelyekkel lekezelhető, hogy feliratkozáskor és leiratkozáskor valójában mi történjen.
Nézzünk egy egyszerű példát, ami bemutatja az események használatát:
using System;
namespace PeldaEvent
{
//kezelő delegate
public delegate void SzamValtozott(int ujSzam);
public class EventSource
{
private int _szam;
public event SzamValtozott SzamMegvaltozott;
public EventSource()
{
_szam = 0;
}
public void DoValami()
{
_szam += DateTime.Now.Second;
//Esemény trigger
//Előtte null check kell, hogy feliratkozó van-e!
SzamMegvaltozott?.Invoke(_szam);
}
}
class Program
{
static void Main(string[] args)
{
var eventSource = new EventSource();
//Feliratkozás
eventSource.SzamMegvaltozott += EventSource_SzamMegvaltozott;
//3 trigger
eventSource.DoValami();
eventSource.DoValami();
eventSource.DoValami();
Console.ReadKey();
//Leiratkozás
eventSource.SzamMegvaltozott -= EventSource_SzamMegvaltozott;
}
private static void EventSource_SzamMegvaltozott(int ujSzam)
{
Console.WriteLine(ujSzam);
}
}
}
A program egy lehetséges kimenete:
16
32
48
Mint látható, események esetén szükséges egy delegate, ami az eseménykezelÅ‘ metódus definÃcióját Ãrja le. Itt megjegyezném, hogy saját tÃpusok alkalmazása helyett érdemes a keretrendszer EventHandler delegate tÃpusát alkalmazni. Ennek elmulasztása komoly tervezési probléma, mivel a keretrendszerben minden esemény nagyjából ezt használja, Ãgy elvárható bármilyen komponenstÅ‘l, hogy a megszokott, jól bevált módszereket alkalmazza.
A beépÃtett EventHandler delegate tÃpus két paramétert definiál az eseményeknek: egy object tÃpust, ami a feladóra mutató referencia és egy EventArgs tÃpusú objektum, ami az eseménynek közvetÃt paramétereket. Ez egy alap osztály, ami alap esetben nem tartalmaz semmi hasznosat, viszont kiegészÃthetÅ‘ és leszármaztatható további argumentumok tárolására.
Az alábbi példa az egyszerű EventHandler tÃpusú esemény használatát mutatja be:
using System;
namespace PeldaEvent3
{
public class EventSource
{
//Tulajdonság az állapot eléréséhez
public int Szam { get; private set; }
public event EventHandler SzamMegvaltozott;
public EventSource()
{
Szam = 0;
}
public void DoValami()
{
Szam += DateTime.Now.Second;
//Esemény trigger
//Előtte null check kell, hogy feliratkozó van-e!
//EventArgs.Empty-t adunk át, a null érték kerülendő!
SzamMegvaltozott?.Invoke(this, EventArgs.Empty);
}
}
class Program
{
static void Main(string[] args)
{
var eventSource = new EventSource();
//Feliratkozás
eventSource.SzamMegvaltozott += EventSource_SzamMegvaltozott;
//3 trigger
eventSource.DoValami();
eventSource.DoValami();
eventSource.DoValami();
Console.ReadKey();
//Leiratkozás
eventSource.SzamMegvaltozott -= EventSource_SzamMegvaltozott;
}
private static void EventSource_SzamMegvaltozott(object sender, EventArgs e)
{
var felado = sender as EventSource;
if (felado!=null)
{
//A feladótól kérdezzük le az értéket.
//Itt az eventHandler csak értesÃt bennünket.
Console.WriteLine(felado.Szam);
}
}
}
}
A program egy lehetséges kimenete:
37
74
111
A C# 2.0-ás változatában bemutatkoztak a generikusok, Ãgy van generikus Event handler minta is. A .NET ajánlás azonban kompatibilitási okok miatt még mindig az lenne, hogy inkább az EventArgs osztályt származtassuk le és azzal közöljünk paramétereket publikusan közzétett események esetén, fÅ‘leg ha egy esemény esetén több paramétert át szeretnénk juttatni az eseménykezelÅ‘nek. Az alábbi példa a generikus megoldást mutatja be saját EventArgs segÃtségével.
using System;
namespace PeldaEvent2
{
public class SzamEventArgs: EventArgs
{
public int Szam { get; }
public SzamEventArgs(int szam)
{
Szam = szam;
}
}
public class EventSource
{
private int _szam;
public event EventHandler<SzamEventArgs> SzamMegvaltozott;
public EventSource()
{
_szam = 0;
}
public void DoValami()
{
_szam += DateTime.Now.Second;
//Esemény trigger
//Előtte null check kell, hogy feliratkozó van-e!
SzamMegvaltozott?.Invoke(this, new SzamEventArgs(_szam));
}
}
class Program
{
static void Main(string[] args)
{
var eventSource = new EventSource();
//Feliratkozás
eventSource.SzamMegvaltozott += EventSource_SzamMegvaltozott;
//3 trigger
eventSource.DoValami();
eventSource.DoValami();
eventSource.DoValami();
Console.ReadKey();
//Leiratkozás
eventSource.SzamMegvaltozott -= EventSource_SzamMegvaltozott;
}
private static void EventSource_SzamMegvaltozott(object sender, SzamEventArgs e)
{
Console.WriteLine(e.Szam);
}
}
}
A program egy lehetséges kimenete:
50
100
150
Mikor kell leiratkozni egy eseményről?
A delegate-ek használatánál elég jól ki lett hangsúlyozva, hogy ha nem jól csináljuk a dolgokat, akkor memória szivárgást kapunk. Ugyanez igaz eseményekre is. Azonban nem minden esetben kell leiratkoznunk, van, amikor ez nem okoz bajt.
Abban az esetben nincs baj, ha X osztály belsÅ‘ változóként hozza létre Y osztályt és feliratkozik Y osztály eseményeire, valamint Y osztály az X osztályon kÃvül nem látható.
Ebben az esetben X osztály megszűnésekor az egyetlen referencia Y osztályra az X, ami valójában Y létrehozója. A GC ezt érzékelni tudja és eltakarÃtja a referenciát, ami nem fog problémát okozni.
Egyedi feliratkozás és leiratkozás kezelés add és remove segÃtségével
Egyedi feliratkozás kezelés megvalósÃtására akkor van szükség, ha az osztályunknak szál biztosnak kell lennie, vagyis egy idÅ‘ben többen is feliratkozhatnak, illetve leiratkozhatnak az eseményrÅ‘l. Ennek megvalósÃtásáról a több szálú programozás tárgyalásánál lesz szó.
-
Az Observer, vagy MegfigyelÅ‘ minta egy olyan szoftvertervezési minta, melyben egy objektum, melyet alanynak hÃvunk, listát vezet alárendeltjeirÅ‘l, akiket megfigyelÅ‘knek hÃvunk és automatikusan értesÃti Å‘ket bármilyen állapotváltozásról, többnyire valamely metódusuk meghÃvásán keresztül. Többnyire elosztott eseménykezelÅ‘ rendszerek kialakÃtásakor használjuk. – https://hu.wikipedia.org/wiki/Megfigyel%C5%91_programtervez%C3%A9si_minta↩