Sunteți pe pagina 1din 29

Delegates

Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21.01.2007

Delegate
(Applied Microsoft .NET Framework Programming J. Richter)
Delegate suporta apelul metodelor statice si al metodelor instantei.
Metode callback folosite in tratarea notificarilor, exceptiilor, schimbarea starii ferestrelor,
selectie articole de meniu, operatii asincrone.

Declarare, creare si folosire delegate


Etape
Se defineste tipul delegate:
1. public delegate void nume_delegate(lista_parametri);
2. Definire metoda ce are un parametru de tip nume_delegate
public void Metoda_delgate(nume_delegate fct_callback)
3. Apel metoda ce are parametrii dati la etapa 1. (lista_parametri)
if (fct_callback != null)
fct_callback(lista_parametri)
Pentru a apela functia callback, Functie_callback, va trebui sa scriem o asemenea
functie in clasa noastra si apoi apelam Metoda_delegate furnizandu-i ca parametru
functia noastra.
Metoda_delegate(new obiect.Functie_callback);
Se disting doua situatii: metode callback statice si metode callback de instanta.
Le vom trata separat.

Vom explica aceste probleme pe baza exemplului de mai jos.


using
using
using
class
{

System;
System.Windows.Forms;
System.IO;
Set
private Object[] items;
public Set(Int32 numItems)
{
items = new Object[numItems];

Ioan Asiminoaei

Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21.01.2007

for (Int32 i = 0; i < numItems; i++)


items[i] = i;
}
// Definim tipul FeedBack. (delegate)
// ATENTIUNE! Tipul se defineste in cadrul clasei Set
// In fapt este un prototip de functie (metoda).

public delegate void Feedback(Object value,


Int32 item,
Int32 numItems);
//
// Definim metoda ce are un parametru de tip FeedBack
//
public void ProcessItems(Feedback feedback)
{
for (Int32 item = 0; item < items.Length; item++)
{
if (feedback != null)
{
//
// Daca s-a specificat o functie callback,
// atunci o apelam.
//
feedback(items[item], item + 1, items.Length);
}
}
}
}

class App
{
static void Main()
{
StaticCallbacks();
InstanceCallbacks();
}
static void StaticCallbacks()
{
// Creem un tablou cu 5 elemente de tip Set
Set setOfItems = new Set(5);
// Procesam articolele fara a furniza o metoda callback
setOfItems.ProcessItems(null);
Console.WriteLine();
//
// Procesam articolele si furnizam metoda
// callback FeedbackToConsole
//
setOfItems.ProcessItems(new Set.Feedback(App.FeedbackToConsole));
Console.WriteLine();

Ioan Asiminoaei

Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21.01.2007

// Procesam articolele fara a furniza o alta metoda callback


setOfItems.ProcessItems(new Set.Feedback(App.FeedbackToMsgBox));
Console.WriteLine();
// Procesam articolele, si folosim doua metode callback:
// FeedbackToConsole si FeedbackToMsgBox.
// Pentru fiecare articol se apeleaza ambele metode.
// Se creaza o lista de metode callback.
Set.Feedback fb = null;
fb += new Set.Feedback(App.FeedbackToConsole);
fb += new Set.Feedback(App.FeedbackToMsgBox);
setOfItems.ProcessItems(fb);
Console.WriteLine();
}
//
// este private
//
static void FeedbackToConsole(
Object value, Int32 item, Int32 numItems)
{
Console.WriteLine("Processing item {0} of {1}: {2}.",
item, numItems, value);
}
//
// este private
//

static void FeedbackToMsgBox( Object value, Int32 item,


Int32 numItems)
{
MessageBox.Show(String.Format("Processing item {0} of {1}: {2}.",
item, numItems, value));
}
static void InstanceCallbacks()
{
// Creaza o multime cu 5 elemente.
Set setOfItems = new Set(5);
// Proceseaza articolele, si apeleaza
// metoda callback FeedbackToFile.
App appobj = new App();
setOfItems.ProcessItems(new Set.Feedback(appobj.FeedbackToFile));
Console.WriteLine();
}
//
// este private
//

void FeedbackToFile( Object value, Int32 item, Int32 numItems)


{

Ioan Asiminoaei

Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21.01.2007

StreamWriter sw = new StreamWriter("Status", true);


sw.WriteLine("Processing item {0} of {1}: {2}.", item, numItems,
value);
sw.Close();
}
}

Descriere cod
Clasa Set constructorul creaza un tablou de obiecte, tablou cu dimensiune specificata.
Intializeaza fiecare element al tabloului cu un numar intreg.
Clasa Set defineste ca public delegate: Feedback.
Tot public se defineste si metoda ProcessItem ce are ca parametru o referinta la un
obiect delegate Feedback. Aceasta metoda scaneaza tabloul de elemente si pentru fiecare
element este apelata metoda callback.

Delegate si metode statice callback


Atentia ne va fi indreptata asupra metodei StaticCallbacks definita mai sus.
FeedbackToConsole este o metoda privata, si este apelata de ProccesItem din
clasa Set. Nu apare nici o problema legata de securitate.
Obiectul delegate este un wrapper pentru metoda, iar metoda va fi apelata indirect via
acest wrapper.
Urmatorul cod:
// Procesam articolele, si folosim doua metode callback:
// FeedbackToConsole si FeedbackToMsgBox.
// Pentru fiecare articol se apeleaza ambele metode.
// Se creaza o lista de metode callback.
//
// Se defineste o variabila fb de tip Set.Feedback
// Atentiune! Nu se apeleaza constructorul.
//
Set.Feedback fb = null;
//
// Se construieste o lista inlantuita de delegate
// Operatorul += face acest lucru. Adauga elemente la
// lista inlantuita.
// Primul element din lista
//
fb += new Set.Feedback(App.FeedbackToConsole);
//
// Al doilea element din lista
//
fb += new Set.Feedback(App.FeedbackToMsgBox);
//
// Apel callback din lista inlantuita
//
setOfItems.ProcessItems(fb);
Console.WriteLine();

Ioan Asiminoaei

Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21.01.2007

creaza o lista de metode callback.


Vezi comentariile pentru explicatii.

Folosire delegate pentru apelul metodelor de instanta


Codul ce il vom examina se gaseste in metoda InstanceCallbacks.
static void InstanceCallbacks()
{
// Creaza o multime cu 5 elemente
Set setOfItems = new Set(5);
// Proceseaza articolele si apeleaza metoda FeedbackToFile.
App appobj = new App();
setOfItems.ProcessItems(new Set.Feedback(appobj.FeedbackToFile));
Console.WriteLine();
}

Cand un nou obiect delegate Feedback este creat, se paseaza constructorului acestuia
appobj.FeedbackToFile. Efectul este ca delegate va construi o referinta la
metoda FeedbackToFile, care este o metoda a instantei (nu este o metoda statica). Cand
aceasta metoda a instantei este apelata, obiectul appobj se refera la obiectul pe care se va
lucra (pointer this).
Pentru metodele instantei, delegate are nevoie sa stie instanta obiectului ce contine
metoda.

Delegates in detaliu
Din punct de vedere al clientului: definim delegate, construim instanta si apelam metoda
(callback).
Din punctul de vedere al compilatorului si al CLR-ului lucrurile sunt mai complicate.
Vom examina codul:
public delegate void Feedback( Object value,
Int32 item,
Int32 numItems);
La intilnirea acestei linii de cod compilatorul va defini o clasa ce arata cam asa
(documentatie Microsoft):
public class Feedback : System.MulticastDelegate
{
// Constructor
public Feedback(Object target, Int32 methodPtr);

Ioan Asiminoaei

Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21.01.2007

// Method with same prototype as specified by the source code


public void virtual Invoke( Object value,
Int32 item,
Int32 numItems);
// Methods allowing the callback to be called asynchronously
public virtual IAsyncResult BeginInvoke(
Object value,
Int32 item,
Int32 numItems,
AsyncCallback callback,
Object object);
public virtual void EndInvoke(IAsyncResult result);
}

Vom discuta constructorul si metoda Invoke. Pentru a va convinge ca asa stau lucrurile
folositi ILDasm.exe.
Toate tipurile delegate sunt derivate din System.MulticastDelegate.
Clasa Feedback este derivata din System.MulticastDelegate, si numai compilatorul poate
face acest lucru cand intilneste cuvantul cheie delegate.
Modificatorul de acces al clasei este acelasi cu modificatorul de acces folosit la
declararea delegate.
Avem de a face cu o clasa definita in interiorul altei clase.
Campuri principale din System.MulticastDelegate
Camp
Tip
Descriere
_target
System.Object
Se refera la obiectul pe care se va lucra cand
metoda callback va fi apelata. Acest camp
este folosit pentru metode callback ale
instantei.
_methodPtr
System.Int32
Un intreg folosit intern de CLR pentru a
identifica metoda callback apelata.
_prev
System.MulticastDelegate Se refera la un alt obiect delegate. In mod
normal are valoarea null. Este folosit pentru
a crea o lista inlantuita de obiecte
MulticastDelegate.
Constructorii pentru delegate au doi parametri: o referinta la un obiect si un intreg ce
identifica metoda callback.
Secventa de cod ce transmite metoda callback nu foloseste forma ctor cu doi parametri :
Set setOfItems = new Set(5);
// Proceseaza articolele si apeleaza metoda FeedbackToFile.

Ioan Asiminoaei

Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21.01.2007

App appobj = new App();


setOfItems.ProcessItems(new Set.Feedback(appobj.FeedbackToFile));

In acest caz compilatorul adauga cod pentru identificarea metodei, mai precis pentru a
construi al doilea parametru al constructorului.
Pentru metodele instantei _target retine referinta la obiect, iar pentru metodele callback
statice, _target = null.
MulticastDelegate defineste doua proprietati: Target si Method.
Proprietatea Target returneaza o referinta la obiectul pe care se va lucra daca metoda
callback apelata este o metoda a instantei.
Daca metoda este statica Target returneaza null.
Proprietatea Method returneaza un obiect System.Reflection.MethodInfo,
obiect ce identifica metoda callback.
Putem folosi aceasta informatie in mai multe moduri. De exemplu, putem verifica daca
un obiect delegate se refera la metoda instanta a clasei de un anume tip :
Boolean DelegateRefersToInstanceMethodOfType(
MulticastDelegate d, Type type)
{
return((d.Target != null) && d.Target.GetType() == type);
}

Putem verifica daca metoda callback are un anumit nume:


Boolean DelegateRefersToMethodOfName( MulticastDelegate d,
String methodName)
{
return(d.Method.Name == methodName);
}

Invocarea metodelor callback


Reluam ProcessItem din clasa Set.
public void ProcessItems(Feedback feedback)
{
for (Int32 item = 0; item < items.Length; item++)
{
if (feedback != null)
{
//
// Daca s-a specificat o functie callback,
// atunci o apelam.
//

Ioan Asiminoaei

Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21.01.2007

feedback(items[item], item, items.Length);


}
}
}

Dupa cum se observa apelam o metoda cu numele feedback, desi nu am definit asa ceva.
Din declratie feedback este o variabila ce se refera la un obiect delegate.
Compilatorul va genera cod pentru a apela metoda Invoke a obiectului delegate.
Pentru linia de cod :
feedback(items[item], item, items.Length);
compilatorul va genera ceva de genul:
feedback.Invoke(items[item], item, items.Length);
Metoda Invoke nu poate fi apelata direct din codul sursa, compilator C#.
In Visual Basic trebuie apelata explicit metoda Invoke.
Metoda Invoke foloseste campurile _target si _methodPtr pentru a apela metoda corecta
pe obiectul specificat.
Metoda Invoke are aceeasi signatura ca si delegate.

Comparare delegate pentru egalitate


Clasa de baza Delegate suprascrie metoda Equals din System.Object.
Metoda Equals compara doua obiecte delegate pentru a vedea daca campurile _target
si _methodPtr se refera la acelasi obiect si aceeasi metoda, altfel spus doua obiecte
delegate sunt egale daca fac refrinta la acelasi obiect si la aceeasi metoda.
Exemplu
Feedback fb1 = new Feedback(FeedbackToConsole);
Feedback fb2 = new Feedback(FeedbackToConsole);
//
// Chiar daca fb1 si fb2 se refera la doua obiecte diferite,
// intern ele refera acelasi obiect si aceeasi metoda.
//
Console.WriteLine(fb1.Equals(fb2)); // Afiseaza "True"

In plus tipurile Delegate si MulticastDelegate supraincarca operatorii == si != (trebuie


supraincarcati impreuna), ca in exemplul urmator:
Feedback fb1 = new Feedback(FeedbackToConsole);
Feedback fb2 = new Feedback(FeedbackToConsole);
Console.WriteLine(fb1 == fb2); // Afiseaza "True"

Putem folosi acesti operatori in locul metodei Equals.


Ioan Asiminoaei

Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21.01.2007

Lanturi (liste) de delegate (Delegates Chains)


Pe acelasi obiect apelam mai multe metode callback. Campul _prev din
MulticastDelegate este folosit pentru a mentine lista inlantuita de delegate.
Clasa Delegate defineste urmatoarele metode pe care le putem folosi in manipularea listei
inlantuite de obiecte delegate.
class System.Delegate
{
// Combines the chains represented by head and tail;
// head is returned.
// NOTE: head will be the last delegate called.
public static Delegate Combine(Delegate tail, Delegate head);
// Creates a chain represented by the array of delegates.
// NOTE: entry 0 is the head
// and will be the last delegate called.
public static Delegate Combine(Delegate[] delegateArray);
// Removes a delegate matching values
// Target/Method from the chain.
// The new head is returned and will be the last delegate called.
public static Delegate Remove(Delegate source, Delegate value);

}
Cand se construieste un obiect delegate, campul _prev = null. Pentru a construi lista
inlantuita de delegate folosim metodele Combine, ca mai jos :
Feedback fb1 = new Feedback(FeedbackToConsole);
Feedback fb2 = new Feedback(FeedbackToMsgBox);
Feedback fbChain = (Feedback) Delegate.Combine(fb1, fb2);
App appobj = new App();
Feedback fb3 = new Feedback(appobj.FeedbackToStream);
fbChain = (Feedback) Delegate.Combine(fbChain, fb3);

Metoda Combine, varianta a doua:


Feedback[] fbArray = new Feedback[3];
fbArray[0] = new Feedback(FeedbackToConsole);
fbArray[1] = new Feedback(FeedbackToMsgBox);
App appobj = new App();
fbArray[2] = new Feedback(appobj.FeedbackToStream);
Feedback fbChain = Delegate.Combine(fbArray);

Tipul delegate Feedback returneaza void.


Daca acest tip ar returna (de ex. Int32) prototipul este :
Ioan Asiminoaei

Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21.01.2007

public delegate Int32 Feedback( Object value,


Int32 item,
Int32 numItems);

iar metoda interna Invoke va arata astfel:


class Feedback : MulticastDelegate
{
public Int32 virtual Invoke(Object value,
Int32 item,
Int32 numItems)
{
// If there are any delegates in the chain that
// should be called first, call them.
if (_prev != null) _prev.Invoke(value, item, numItems);
// Call the callback method on the specified target object.
return _target.methodPtr(value, item, numItems);
}
}

Daca delegate returneaza o valoare, si avem o lista de delegate, numai delegate din capul
listei va returna valoarea.

Stergerea unui delegate dintr-o lista


Metoda folosita este Remove.
Vezi exemplul urmator:
Feedback fb1 = new Feedback(FeedbackToConsole);
Feedback fb2 = new Feedback(FeedbackToMsgBox);
Feedback fbChain = (Feedback) Delegate.Combine(fb1, fb2);
// fbChain se refera la o lista cu doi delegate.
// Se apeleaza cele doua metode callback.
if (fbChain != null) fbChain(null, 0, 10);
fbChain = (Feedback) Delegate.Remove(
fbChain,
new Feedback(FeedbackToMsgBox));
// s-a sters o metoda din lista.
// Se va apela singura metoda ce a mai ramas.
if (fbChain != null) fbChain(null, 0, 10);

Se sterge cate o singura metoda.


Operatorul += este sinonim cu Combine, iar -= este sinonim cu Remove.
Feedback fb = new Feedback(FeedbackToConsole);
App appobj = new App();
fb += new Feedback(appobj.FeedbackToStream);
...

Ioan Asiminoaei

Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21.01.2007

fb -= new Feedback(FeedbackToConsole);

Mai mult control asupra metodei Invoke


Neajunsuri: se poate prelua numai o singura valoare returnata, cea returnata de ultima
metoda callback apelata.
Ce se intimpla daca o metoda callback arunca o exceptie sau ia mult timp procesor pentru
executie?
Pentru situatiile cand algoritmul de executie al metodelor callback (algoritm serial) nu
este suficient de bun, putem folosi metoda de instanta GetInvocationList din
MulticastDelegate. Aceasta metoda o putem folosi pentru a apela fiecare delegate
in mod explicit dintr-o lista de delegates.
public class MulticastDelegate
{
// Creates a delegate array; each item is a clone from the chain.
// NOTE: entry 0 is the tail, which would normally be called first.
public virtual Delegate[] GetInvocationList();
}

Metoda GetInvocationList opereaza cu referinte la delegate si returneaza un tablou


de referinte la delegates. Se creaza o clona pentru fiecare obiect delegate din lista.
Fiecare clona are campul _prev = null.
Exemplu (J. Richter):
using System;
using System.Text;
// Define a Light component.
class Light
{
// This method returns the lights status.
public String SwitchPosition()
{
return "The light is off";
}
}
// Define a Fan component.
class Fan
{
// This method returns the fans status.
public String Speed()
{
throw new Exception("The fan broke due to overheating");
}
}
// Define a Speaker component.

Ioan Asiminoaei

Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21.01.2007

class Speaker
{
// This method returns the speakers status.
public String Volume()
{
return "The volume is loud";
}
}
class App
{
// Definition of delegate that allows querying a components status
delegate String GetStatus();
static void Main()
{
// Declare an empty delegate chain.
GetStatus getStatus = null;
// Construct the three components, and add
// their status methods
// to the delegate chain.
getStatus += new GetStatus(new Light().SwitchPosition);
getStatus += new GetStatus(new Fan().Speed);
getStatus += new GetStatus(new Speaker().Volume);
// Show consolidated status report reflecting
// the condition of the three components.
Console.WriteLine(GetComponentStatusReport(getStatus));
}
// Method that queries several components
// and returns a status report
static String GetComponentStatusReport(GetStatus status)
{
// If the chain is empty, theres nothing to do.
if (status == null) return null;
// Use this to build the status report.
StringBuilder report = new StringBuilder();
// Get an array where each element
// is a delegate from the chain.
Delegate[] arrayOfDelegates = status.GetInvocationList();
// Iterate over each delegate in the array.
foreach (GetStatus getStatus in arrayOfDelegates)
{
try
{
// Get a components status string,
// and append it to the report.

Ioan Asiminoaei

Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21.01.2007

report.AppendFormat("{0}{1}{1}",
getStatus(), Environment.NewLine);
}
catch (Exception e)
{
// Generate an error entry in the report
// for this component.
Object component = getStatus.Target;
report.AppendFormat(
"Failed to get status from
{1}{2}{0} Error: {3}{0}{0}",
Environment.NewLine,
((component == null) ? "" :
component.GetType() + "."),
getStatus.Method.Name, e.Message);
}
}
// Return the consolidated report to the caller.
return report.ToString();
}
}

Rezultatul este:
The light is off
Failed to get status from Fan.Speed
Error: The fan broke due to overheating
The volume is loud

Delegate si Reflection
In metodele callback discutate pina acum, informatia necesara (parametrii) erau stiuti in
momentul compilarii.
Tratam problema completarii acestor parametri in momentul executiei plus problema
apelarii unei anumite metode callback. Este exact ceea ce intimpla in programarea
Windows. Un eveniment genereaza un mesaj, daca exista un handler pentru mesaj acesta
va fi tratat, altfel nu.
Metodele din clasa delegate ce realizeaza acest lucru sunt:
public class Delegate
{
// Construct a delType delegate wrapping
// the specified methodInfo.
public static Delegate CreateDelegate(
Type delType,
MethodInfo mi);

Ioan Asiminoaei

Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21.01.2007

// Construct a delType delegate wrapping a types static method.


public static Delegate CreateDelegate(
Type delType,
Type type,
String methodName);
// Construct a delType delegate wrapping
// an objects instance method.
public static Delegate CreateDelegate(
Type delegateType,
Object obj,
String methodName);
// Construct a delType delegate wrapping
// an objects instance method.
public static Delegate CreateDelegate(
Type delegateType,
Object obj,
String methodName,
Boolean ignoreCase);
public Object DynamicInvoke(Object[] args);
}

Toate metodele CreateDelegate construiesc un nou obiect de tip Delegate,


identificat de primul parametru, delType.
Ceilalti parametri determina metoda callback pe care obiectul derivat din Delegate o va
implementa.
Metoda DynamicInvoke ne permite sa invocam o metoda callback a obiectului
delegate, pasindu-i parametrii la run time.
DynamicInvoke face un control al parametrilor (acelasi numar, acelasi tip, aceeasi
pozitie) pentru metoda callback invocata. In caz de esec se arunca o exceptie.
DynamicInvoke returneaza obiectul returnat de metoda callback.
Exemplu:
using System;
using System.Reflection;
using System.IO;
// Here are some different delegate definitions.
delegate Object TwoInt32s(Int32 n1, Int32 n2);
delegate Object OneString(String s1);
class App

Ioan Asiminoaei

Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21.01.2007

{
static void Main(String[] args)
{
if (args.Length < 2)
{
String fileName = Path.GetFileNameWithoutExtension(
Assembly.GetEntryAssembly().CodeBase);
Console.WriteLine("Usage:");
Console.WriteLine("{0} delType methodName [Param1] [Param2]",
fileName);
Console.WriteLine(" where delType must be TwoInt32s or
OneString");
Console.WriteLine(" if delType is TwoInt32s, " +
"methodName must be Add or Subtract");
Console.WriteLine(" if delType is OneString, " +
"methodName must be NumChars or Reverse");
Console.WriteLine();
Console.WriteLine("Examples:");
Console.WriteLine(" {0} TwoInt32s Add 123 321", fileName);
Console.WriteLine(" {0} TwoInt32s Subtract 123 321", fileName);
Console.WriteLine(" {0} OneString NumChars \"Hello there\"",
fileName);
Console.WriteLine(" {0} OneString Reverse \"Hello there\"",
fileName);
return;
}
Type delType = Type.GetType(args[0]);
if (delType == null)
{
Console.WriteLine("Invalid delType argument: " + args[0]);
return;
}
Delegate d;
try
{
d = Delegate.CreateDelegate(delType, typeof(App), args[1]);
}
catch (ArgumentException)
{
Console.WriteLine("Invalid methodName argument: " +
args[1]);
return;
}
Object[] callbackArgs = new Object[args.Length - 2];
if (d.GetType() == typeof(TwoInt32s))
{
try
{
for (Int32 a = 2; a < args.Length; a++)
callbackArgs[a - 2] = Int32.Parse(args[a]);
}
catch (FormatException)
{

Ioan Asiminoaei

Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21.01.2007

Console.WriteLine("Parameters must be integers.");


return;
}
}
if (d.GetType() == typeof(OneString))
{
Array.Copy(args, 2, callbackArgs, 0, callbackArgs.Length);
}
try
{
Object result = d.DynamicInvoke(callbackArgs);
Console.WriteLine("Result = " + result);
}
catch (TargetParameterCountException)
{
Console.WriteLine("Incorrect number of parameters +
specified.");
}
}
// This is the callback method that takes two Int32 parameters.
static Object Add(Int32 n1, Int32 n2) {
return n1 + n2;
}
static Object Subtract(Int32 n1, Int32 n2) {
return n1 - n2;
}
// This is the callback method that takes one String parameter.
static Object NumChars(String s1) {
return s1.Length;
}
static Object Reverse(String s1) {
Char[] chars = s1.ToCharArray();
Array.Reverse(chars);
return new String(chars);
}
}

Ioan Asiminoaei

Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21.01.2007

Definirea evenimentelor cu MulticastDelegates


Aplicatiile Windows au nevoie sa proceseze evenimente in mod asincron. Evenimentele
genereaza mesaje, iar mesajele au atasate functii ce le trateaza.
Modelul delegate multicast are la baza paternul Publish/Subscribe, in care o clasa
publica un eveniment pe care-l poate genera, si un alt numar de clase subscriu pentru
acest eveniment.
Cand evenimentul a fost generat, runtime-ul are grija sa notifice producerea
evenimentului fiecarei clase ce asteapta acel eveniment.
Metoda apelata ca rezultat al producerii unui eveniment este definita de un delegate.
Cand folosim delegate in acest mod, trebuie sa respectam urmatoarele reguli:
1. Delegate trebuie definit ca avand doi parametri.
2. Argumentele reprezinta totdeauna doua obiecte: obiectul ce a generat
evenimentul, si un obiect eveniment.
3. Al doilea parametru trebuie sa fie derivat din clasa EventArgs.
Exemplificam pe baza urmatorului exemplu. Vrem sa avem posibilitatea monitorizarii
corecte a schimbarilor de stocuri dintr-o magazie de marfa. Modificarea stocului poate fi
facuta prin intermediul urmatoarelor actiuni: aprovizionare, vinzare, inventar faptic.
O idee de a implementa acest lucru este de a construi o clasa ce va fi folosita totdeauna
pentru actualizarea stocului.
Aceasta clasa ar trebui sa publice un eveniment ce va fi generat ori de cite ori stocul se
modifica conform actiunilor descrise mai sus.
Atunci orice clasa ce are nevoie sa fie actualizata ar trebui sa subscrie pentru acest
eveniment.
Codul ar putea fi (Tom Archer: Inside C# Second Edition):
using System;
class InventoryChangeEventArgs : EventArgs
{
//
// Schimbare stoc din inventar
//
public InventoryChangeEventArgs(string sku, int change)
{
this.sku = sku;
this.change = change;
}
string sku;
public string Sku
{
get { return sku; }
}
int change;
public int Change

Ioan Asiminoaei

Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21.01.2007

{
get { return change; }
}
};

// Publisher

class InventoryManager
{
public delegate void InventoryChangeEventHandler(
object source,
InventoryChangeEventArgs e);

public event InventoryChangeEventHandler


OnInventoryChangeHandler;
public void UpdateInventory(string sku, int change)
{
if (0 == change)
return; // No update on null change.
// Code to update database would go here.
InventoryChangeEventArgs e =
new InventoryChangeEventArgs(sku, change);
if (OnInventoryChangeHandler != null)
{
Console.WriteLine("[InventoryManager" +
".UpdateInventory] Raising event to " +
"all subscribers...\n");

OnInventoryChangeHandler(this, e);
}
}
};

// Subscriber

class InventoryWatcher
{
InventoryManager inventoryManager;
public InventoryWatcher(InventoryManager inventoryManager)
{
Console.WriteLine("[InventoryWatcher" +
".InventoryWatcher] Subscribing to " +
"InventoryChange event\n");
this.inventoryManager = inventoryManager;
inventoryManager.OnInventoryChangeHandler += new
InventoryManager.InventoryChangeEventHandler(OnInventoryChange);
}
// Metoda apelata la eveniment
void OnInventoryChange(object source,
InventoryChangeEventArgs e)

Ioan Asiminoaei

Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21.01.2007

{
int change = e.Change;
Console.WriteLine("[InventoryManager.OnInventoryChange]" +
"\n\tPart '{0}' was {1} by {2} units\n",
e.Sku,
change > 0 ? "increased" : "decreased",
Math.Abs(e.Change));
}
}
// test tipuri definite

class DelegateEvents
{
public static void Main()
{
InventoryManager inventoryManager =
new InventoryManager();
Console.WriteLine("[DelegateEvents.Main] " +
"Instantiating subscriber object\n");
InventoryWatcher inventoryWatch =
new InventoryWatcher(inventoryManager);
inventoryManager.UpdateInventory("111 006 116", -2);
inventoryManager.UpdateInventory("111 005 383", 5);
Console.ReadLine();
}
};

Sa examinam acest cod.


public delegate void InventoryChangeEventHandler
(object source, InventoryChangeEventArgs e);

Am definit un delegate, deci o signatura pentru o metoda. Atentie la numarul de


parametri si tipul acestora.
public event InventoryChangeEventHandler OnInventoryChangeHandler;

Aici apare cuvatul cheie event, ce defineste un membru de tip event, cu care specificam
delegate si metoda(ele) ve va (vor) fi apelata (e) cand evenimentul este generat.
In exemplul de mai sus UpdateInventory va fi apelata ori de cite ori se genereaza
evenimentul ce anunta modificarea stocului.
Aceasta metoda creaza un obiect de tipul InventoryChangeEventArgs. Acest obiect este
pasat tuturor celor ce au subscris pentru acest eveniment, si este utilizat sa descrie
evenimentul.
In urmatorul cod:
if (OnInventoryChangeHandler != null)

Ioan Asiminoaei

Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21.01.2007

{
Console.WriteLine("[InventoryManager" +
".UpdateInventory] Raising event to " +
"all subscribers...\n");
OnInventoryChangeHandler(this, e);
}

instructiunea if verifica daca pentru acest eveniment exista obiecte ce asteapta producerea
lui.
Sa examinam codul din cei ce s-au inscris pentru eveniment. In situatia de fata clasa
InventoryWatcher.
Ceea ce are nevoie sa faca aceasta clasa este:
sa instantieze un delegate de tip InventoryManager.InventoryChangeEventHandler si
sa adauge acest delegate la evenimentul
InventoryManager.OnInventoryChangeHandler.
inventoryManager.OnInventoryChangeHandler += new
InventoryManager.InventoryChangeEventHandler(
OnInventoryChange);

Singurul argument utilizat indica numele metodei ce va apelata cand se va produce


evenimentul.
Urmatorul lucru ce trebuie facut este implementarea functiei ce trateaza evenimentul.
In acest caz functia este InventoryWatcher.OnInventoryChange, ce afiseaza un mesaj.
In final, codul ce ruleaza aceasta aplicatie, instantiaza clasele InventoryManager si
InventoryWatcher, si de fiecare data cand metoda InventoryManager.UpdateInventory
este apelata, se genereaza automat un eveniment ce are ca efect apelul metodei
InventoryWatcher.OnInventoryChanged.

Ioan Asiminoaei

Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21.01.2007

Evenimente
Membrul event al unei clase permite acesteia de a notifica alte obiecte ca ceva s-a
intimplat.
Un tip ce defineste un membru event are urmatoarele capabilitati:

capabilitatea obiectelor de a-si inregistra interesul lor pentru un evenimet;


capabilitatea de a renunta la un eveniment (stergerea inregistrarii);
capabilitatea obiectului ce defineste evenimentul de a mentine o lista a obiectelor
inregistrate pentru eveniment si de a le notifica acestora producerea
evenimentului.

Modelul eveniment al CLR-ului este bazat pe delegates.


Consideram urmatorul exemplu. Dorim sa proiectam o aplicatie pentru e-mail.
Cand soseste un mesaj, utilizatorul doreste ca acesta sa fie redirectionat (forward) catre
un fax sau un pager.
Construim urmatoarele tipuri:
MailManager, ce primeste mesajele e-mail. MailManager va expune un eveniment numit
MailMsg.
Alte tipuri (Fax, Pager) trebuie sa-si manifeste interesul fata de acest eveniment.
Cand se primeste un e-mail, MailManager va produce evenimentul, distribuind mesajul
catre clasele ce s-au inregistrat pentru acest mesaj.
Fiecare obiect proceseaza mesajul dupa cum doreste.

Proiectarea unui tip ce expune un eveniment


Pattern-ul recomandat de Microsoft pentru definirea unui eveniment este urmatorul :
Etapa 0 : Definire tip ce va contine event. (class MailManager).
Etapa 1 : Definirea unui tip imbricat, derivat din EventArgs, tip ce va contine
informatia ce va fi pasata obiectelor interesate de acest eveniment (public class
MailMsgEventArgs : EventArgs).
Etapa 2 : Definim tipul delegate ce ne va da prototipul metodei callback pe care
apelatii vor trebui sa o implementeze (

public delegate void MailMsgEventHandler(


Object sender,
MailMsgEventArgs args);
Etapa 3: Declararea datei membru event
public event MailMsgEventHandler MailMsg;
Etapa 4: Definirea metodei responsabila cu notificarea clientilor despre aparitia
evenimentului

protected virtual void OnMailMsg(MailMsgEventArgs e)

Ioan Asiminoaei

Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21.01.2007

Etapa 5: Definirea metodei ce identifica daca evenimentul a aparut sau nu:

public void SimulateArrivingMsg(


String from,
String to,
String subject,
String body)
Exemplu:
class MailManager
{
// The MailMsgEventArgs type is defined within the MailManager
// type.
public class MailMsgEventArgs : EventArgs
{
// 1. Type defining information passed to receivers of the event
public MailMsgEventArgs(
String from, String to, String subject, String body)
{
this.from = from;
this.to = to;
this.subject = subject;
this.body = body;
}
public readonly String from, to, subject, body;
}
// 2. Delegate type defining the prototype of the callback
// method that receivers must implement
public delegate void MailMsgEventHandler(
Object sender, MailMsgEventArgs args);
// 3. The event itself
public event MailMsgEventHandler MailMsg;
// 4. Protected, virtual method responsible for notifying
// registered objects of the event
protected virtual void OnMailMsg(MailMsgEventArgs e)
{
// Has any objects registered interest with the event?
if (MailMsg != null)
{
// Yesnotify all the objects in the delegate linked list.
MailMsg(this, e);
}
}
// 5. Method that translates the input into the desired event.
// This method is called when a new e-mail message arrives.

Ioan Asiminoaei

Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21.01.2007

public void SimulateArrivingMsg(String from, String to,


String subject, String body)
{
// Construct an object to hold the information you want
// to pass to the receivers of the notification.
MailMsgEventArgs e =
new MailMsgEventArgs(from, to, subject, body);
// Call
// that
//If no
// will

the virtual method notifying the object


the event occurred.
derived type overrides this method, the object
notify all the registered listeners.

OnMailMsg(e);
}
}

Codul critic se gaseste in clasa MailManager.


Dezvoltatorul trebuie sa defineasca urmatoarele articole:
1. Sa defineasca un tip ce va mentine informatii aditionale ce ar trebui trimise celor ce
asteapata notificarea evenimentului. Tipurile ce mentin informatia despre un eveniment
sunt derivate din System.EventArgs, si numele tipului ar trebui sa se termine cu
EventArgs. In exemplul nostru, tipul MailMsgEventArgs contine campuri ce idetifica cine
a trimis mesajul (from), cine primeste mesajul (to), subiectul mesajului (subject) si corpul
mesajului (body).
EventArgs este definita in FCL astfel:
[Serializable]
public class EventArgs
{
public static readonly EventArgs Empty = new EventArgs();
public EventArgs() { }
}

Acesta serveste ca un tip de baza din care alte tipuri pot deriva.
Exista si evenimente care nu au informatii aditionale de transmis (clic pe un buton).
Cand definim un eveniment ce nu are informatii aditionale de transmis putem folosi
EventArgs.Empty in loc de a construi un nou obiect EventArgs.
2. Definim un tip delegate, specificand prototipul metodei ce va fi apelata cand se
genereaza evenimentul. Prin conventie numele delegate se termina cu EventHandler.
Prototipul trebuie sa returneze void si sa aiba doi parametri. Primul parametru este un
Object ce se refera la la obiectul ce a trimis notificarea, iar al doilea parametru este un tip
derivat din EventArgs ce contine informatii aditionale despre notificare.
Prototipul pentru EventHandler este urmatorul:

Ioan Asiminoaei

Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21.01.2007

public delegate void EventHandler(Object sender, EventArgs e);

3. Definim un eveniment, MailMsg, de tip MailMsgEventHandler, deci metoda callback


trebuie sa aiba acest prototip.
4. Definim o metoda virtuala, protected, metoda responsabila pentru notificarea
obiectelor inregistrate sa primeasca acest eveniment. Metoda OnMailMsg este apelata
cand un nou mesaj soseste.
Aceasta metoda primeste un obiect MailMsgEventArgs initializat ce contine informatii
aditionale despre eveniment.
Aceasta metoda verifica daca exista cineva interesat de acest eveniment pentru a da
drumul evenimentului.
Un tip ce foloseste MailManager ca tip de baza poate suprascrie metoda OnMailMsg.
5. Definim o metoda ce transforma intrarea in evenimentul dorit.
In exemplul nostru metoda SimulatingArrivingMsg este apelata pentru a indica ca un
mesaj nou a sosit in MailManager.
6. Sa examinam indeaproape ce inseamna sa definim un event MailMsg.
public event MailMsgEventHandler MailMsg;

Compilatorul C# transforma aceasta linie de cod in:


// 1. A PRIVATE delegate field that is initialized to null
// Referinta la inceputul listei de delegates
private MailMsgEventHandler MailMsg = null;
// 2. A PUBLIC add_* method
// Allows objects to register interest in the event.
[MethodImplAttribute(MethodImplOptions.Synchronized)]
public void add_MailMsg(MailMsgEventHandler handler)
{
MailMsg = (MailMsgEventHandler)Delegate.Combine(
MailMsg, handler);
}
// 3. A PUBLIC remove_* method
// Allows objects to unregister interest in the event.
[MethodImplAttribute(MethodImplOptions.Synchronized)]
public void remove_MailMsg(MailMsgEventHandler handler)
{
MailMsg = (MailMsgEventHandler)
Delegate.Remove(MailMsg, handler);
}

Cand un obiect este interesat de eveniment, acest camp referentiaza o instanta a delegate
MailMsgEventHandler.
Fiecare instanta delegate MailMsgEventHandler are un pointer la un alt delegate
MailMsgEventHandler sau la null pentru a marca sfarsitul listei.

Ioan Asiminoaei

Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21.01.2007

Proiectarea unui tip ce asteapta pentru un eveniment


Exemplu
class Fax
{
// Pass the MailManager object to the constructor.
public Fax(MailManager mm)
{
// Construct an instance of the MailMsgEventHandler
// delegate that refers to the FaxMsg callback method.
// Register the callback with MailManagers
// MailMsg event.
mm.MailMsg+= new MailManager.MailMsgEventHandler(FaxMsg);
}
// This is the method that MailManager will call to
// notify the Fax object that a new e-mail message has arrived.
private void FaxMsg(
Object sender, MailManager.MailMsgEventArgs e)
{
// The sender identifies the MailManager in case
// you want to communicate back to it.
// The e identifies the additional event information
// that the MailManager wants to provide.
// Normally, the code here would fax the e-mail message. //
// This test implementation displays the information
// on the console.
Console.WriteLine("Faxing mail message:");
Console.WriteLine(
" To: {0}\n From: {1}\n Subject: {2}\n Body: {3}\n",
e.from, e.to, e.subject, e.body);
}
public void Unregister(MailManager mm)
{
// Construct an instance of the MailMsgEventHandler
// delegate that refers to the FaxMsg callback method.
MailManager.MailMsgEventHandler callback =
new MailManager.MailMsgEventHandler(FaxMsg);
// Unregister with MailManagers MailMsg event.
mm.MailMsg -= callback;
}
}

Cand aplicatia de e-mail se initializeaza, va construi mai intai un obiect MailManager si


va salva referinta la acest obiect intr-o variabila.
In continuare se construieste un obiect de tip Fax, pasindu-i referinta la obiectul
MailManager.
In ctor Fax se construieste un nou obiect delegate MailManager.MailMsgEventHandler
care este un wrapper pentru metoda FaxMsg din Fax ce are acelasi prototip ca
MailMsgEventHandler (altfel codul nu se compileaza).

Ioan Asiminoaei

Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21.01.2007

In continuare obiectul Fax se inregistreaza pentru evenimentul MailMsg din


mailManager :
mm.MailMsg += new MailManager.MailMsgEventHandler(FaxMsg);

Compilatorul C# va transforma operatorul += in urmatoarea linie de cod pentru a adauga


interesul obiectului pentru eveniment :
mm.add_MailMsg(new MailManager.MailMsgEventHandler(FaxMsg));

Aceasta metoda o putem apela si explicit.


Cand MailManager produce evenimentul, obiectul Fax va apela metoda FaxMsg ce
primeste o referinta la un obiect MailManager si o referinta la MailMsgEventArgs.
Referinta la MailManager poate fi folosita pentru a accesa campuri sau metode din
MailManager.
Din obiectul MailMsgEventArgs, metoda FaxMsg are acces la toate caracteristicile
mesajului.

Ioan Asiminoaei

Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21.01.2007

Exemplu cu evenimente (help Microsoft)


/*
* Created by SharpDevelop.
* User: info
* Date: 10/28/2004
* Time: 1:50 PM
*
* To change this template use Tools | Options | Coding | Edit Standard
Headers.
*/
using System;
// FireEventArgs: a custom event inherited from EventArgs.
public class FireEventArgs: EventArgs {
public FireEventArgs(string room, int ferocity) {
this.room = room;
this.ferocity = ferocity;
}
// The fire event will have two pieces of information-// 1) Where the fire is, and 2) how "ferocious" it is.
public string room;
public int ferocity;
}

//end of class FireEventArgs

// Class with a function that creates the eventargs and initiates the
event
public class FireAlarm {
// Events are handled with delegates, so we must establish a
FireEventHandler
// as a delegate:
public delegate void FireEventHandler(object sender, FireEventArgs
fe);
// Now, create a public event "FireEvent" whose type is our
FireEventHandler delegate.
public event FireEventHandler FireEvent;
// This will be the starting point of our event-- it will create
FireEventArgs,
// and then raise the event, passing FireEventArgs.
public void ActivateFireAlarm(string room, int ferocity) {
FireEventArgs fireArgs = new FireEventArgs(room, ferocity);
// Now, raise the event by invoking the delegate. Pass in
// the object that initated the event (this) as well as
FireEventArgs.

Ioan Asiminoaei

Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21.01.2007

// The call must match the signature of FireEventHandler.


FireEvent(this, fireArgs);
}
}

// end of class FireAlarm

// Class which handles the event


class FireHandlerClass {
// Create a FireAlarm to handle and raise the fire events.
public FireHandlerClass(FireAlarm fireAlarm)

// Add a delegate containing the ExtinguishFire function to the


class'
// event so that when FireAlarm is raised, it will subsequently
execute
// ExtinguishFire.
fireAlarm.FireEvent += new
FireAlarm.FireEventHandler(ExtinguishFire);
}
// This is the function to be executed when a fire event is raised.
void ExtinguishFire(object sender, FireEventArgs fe) {
Console.WriteLine("ExtinguishFire function was called by {0}.",
sender.ToString());
// Now, act in response to the event.
if (fe.ferocity < 2)
Console.WriteLine("This fire in the {0} is no problem. I'm
going to pour some water on it.", fe.room);
else if (fe.ferocity < 5)
Console.WriteLine("I'm using FireExtinguisher to put out
the fire in the {0}.", fe.room);
else
Console.WriteLine("The fire in the {0} is out of control.
I'm calling the fire department!", fe.room);
}
}
//end of class FireHandlerClass
public class FireEventTest {
public static void Main ()

// Create an instance of the class that will be firing an


event.
FireAlarm myFireAlarm = new FireAlarm();
// Create an instance of the class that will be handling the
event. Note that

Ioan Asiminoaei

Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21.01.2007

// it receives the class that will fire the event as a


parameter.
FireHandlerClass myFireHandler = new
FireHandlerClass(myFireAlarm);
//use our class to raise a few events and watch them get
handled
myFireAlarm.ActivateFireAlarm("Kitchen", 3);
myFireAlarm.ActivateFireAlarm("Study", 1);
myFireAlarm.ActivateFireAlarm("Porch", 5);
return;
}
}

//end of main
// end of FireEventTest

Ioan Asiminoaei

S-ar putea să vă placă și