Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 1/51
Asiminoaei Ioan
Delegates
Cuprins
Partea a - I - a
Ce este un delegate?
Definirea unui delegate.
Clasele de baza System.MulticastDelegate si System.Delegate.
Invocarea metodelor callback.
Covarianta (relaxed delegates) delegates.
Liste de delegates.
Delegates si Reflection.
Exemple
Partea a – II - a
Apel sincron. Apel asincron.
Delegates anonimi.
Expresii lambda.
Evenimente
Pattern-ul Publish / Subscribe
Exemple.
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 2/51
Asiminoaei Ioan
Ce este un delegate ?
In esenta, un delegate este un obiect type-safe ce puncteaza la o metoda (sau posibil o
lista de metode) din cadrul aplicatiei, metoda ce poate fi invocata mai tarziu. Un obiect
delegate mentine urmatoarele informatii :
• Adresa metodei pe care se face apel;
• Argumentele (daca exista) acestei metode;
• Valoarea de retur (daca exista) a acestei metode.
Dupa ce am creat delegate si am furnizat informatiile necesare, acesta poate invoca in
mod dinamic metoda (metodele) la care puncteaza.
Metodele pot fi apelate sincron sau asincron.
Definirea unui delagate in C#, exemplu:
public delegate void Feedback( Object value,
Int32 item, Int32 numItems);
Prototipul metodei ce poate fi apelat cu acest delegate este: public void Nume_Metoda(object value, int item, int numItems) ;
Cand compilatorul proceseaza acest cod, va genera o clasa sealed derivata din
System.MulticastDelegate.
Clasa poate avea urmatorul prototip (cod incomplet):
class sealed Feedback : System.MulticastDelegate {
// Constructor public Feedback(Object target, Int32 methodPtr); // Metoda cu acelasi prototip ca cel specificat // de codul sursa public void Invoke( Object value,
Int32 item, Int32 numItems);
// Metode ce permit apelul asincron public virtual IAsyncResult BeginInvoke(
Object value, Int32 item, Int32 numItems, AsyncCallback callback, Object object);
public virtual void EndInvoke(IAsyncResult result); }
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 3/51
Asiminoaei Ioan
Observatie
Ce e cu rosu reprezinta parametrii metodei callback. Metodele Invoke si EndInvoke
returneaza void in acest caz.
Metoda Invoke nu o vom apela explicit din cod, desi se poate face si acest lucru (se
apeleaza pe un obiect de tip delegate).
Delegates pot “puncta” la metode ce contin parametri prefixati cu ref si out sau un
array de parametri marcati cu cuvantul cheie params.
Exemplu
public delegate string AltDelegate(out bool a,
ref bool b, int c);
clasa generata ar arata cam asa:
sealed class AltDelegate : System.MulticastDelegate { public AltDelegate (object target, uint functionAddress); public string Invoke(out bool a, ref bool b, int c); public IAsyncResult BeginInvoke(out bool a, ref bool b, int c,
AsyncCallback cb, object state); public string EndInvoke(out bool a, ref bool b,
IAsyncResult result); }
Observatie
Metodele Invoke si EndInvoke returneaza string in acest caz. Observati modul de
creare al clasei AltDelegate.
Pattern-ul de baza folosit de C# la intalnirea unui delegate poate fi urmatorul :
// pseudo-code public sealed class DelegateName : System.MulticastDelegate { // constructor
public DelegateName (object target, uint functionAddress); // metoda Invoke pentru apel sincron public delegateReturnValue Invoke(
allDelegateInputRefAndOutParams ); // metoda pentru apel asincron public IAsyncResult BeginInvoke(
allDelegateInputRefAndOutParams,
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 4/51
Asiminoaei Ioan
AsyncCallback cb, object state);
// metoda pentru terminarea apelului asincron public delegateReturnValue EndInvoke(
allDelegateRefAndOutParams, IAsyncResult result);
}
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 5/51
Asiminoaei Ioan
Clasele de baza System.MulticastDelegate si System.Delegate
O descriere completa o gasiti in MSDN.
public abstract class MulticastDelegate : Delegate {
// Returns the list of methods "pointed to." public sealed override Delegate[] GetInvocationList(); // Overloaded operators. public static bool operator ==(MulticastDelegate d1,
MulticastDelegate d2); public static bool operator !=(MulticastDelegate d1,
MulticastDelegate d2); // Used internally to manage the list of methods // maintained by the delegate. private IntPtr _invocationCount; private object _invocationList;
} public abstract class Delegate : ICloneable, ISerializable {
// Methods to interact with the list of functions. public static Delegate Combine(
params Delegate[] delegates); public static Delegate Combine(Delegate a, Delegate b); public static Delegate Remove(Delegate source,
Delegate value); public static Delegate RemoveAll(Delegate source,
Delegate value); // Overloaded operators. public static bool operator ==(Delegate d1, Delegate d2); public static bool operator !=( Delegate d1, Delegate d2); // Properties that expose the delegate target. public MethodInfo Method { get; } public object Target { get; }
}
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
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 6/51
Asiminoaei Ioan
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 object si un intreg ce
identifica metoda callback.
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.
Invocarea metodelor callback
Exemplu. Metoda ProcessItems are ca parametru un delegate de tip FeedBack – definit la
inceputul cursului. Metoda are ca parametru o referinta la o metoda (in C asta inseamna
pointer la o functie).
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, items.Length);
} }
}
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 7/51
Asiminoaei Ioan
Un alt exemplu simplu de delegate (Andrew Troelsen) namespace SimpleDelegate {
// Acest delegate poate puncta la orice metoda ce are ca // parametri doi intregi si returneaza un intreg. public delegate int BinaryOp(int x, int y); public class SimpleMath {
public static int Add(int x, int y) { return x + y; } public int Subtract(int x, int y) { return x - y; }
}
class Test {
static void Main(string[] args) {
Console.WriteLine(" - Exemplu Delegate -\n"); // Cream un obiect BinaryOp ce puncteaza la // SimpleMath.Add() Metoda statica BinaryOp b = new BinaryOp(SimpleMath.Add); // Invocam metoda Add (indirect) folosind obiectul // delegate. Console.WriteLine("10 + 10 = {0}", b(10, 10)); // Metoda a instantei SimpleMath s = new SimpleMath(); BinaryOp dif = new BinaryOp(s.Subtract); Console.WriteLine(" 20 – 10 = {0}", dif(20,10)); Console.ReadLine();
} } }
Observatie
In loc de apelul
int result = b(10,10) ;
putem scrie si int result = b.Invoke(10, 10);
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 8/51
Asiminoaei Ioan
Vezi exemplele cu delegates (proiect ...) class A { public delegate void D1(string msg); public delegate void D2(string msg); private D1 d1_delegat; private D2 d2_delegat; public void OnD1(D1 metodaClient)
{ d1_delegat = metodaClient; } public void OnD2(D2 metodaClient) { d2_delegat = metodaClient; } public void ApelDelegates() { ... if (d1_delegat != null) d1_delegat("D1!"); ... if (d2_delegat != null) d2_delegat("D2"); }
}
Codul din metoda Main poate fi: A a = new A(); a.OnD1(new A.D1(MainD1)); // d1_delegat = MainD1 a.OnD2(new A.D2(MainD2)); a.ApelDelegates();
si metodele MainD1, MainD2 – in aceeasi clasa cu metoda Main public static void MainD1(string msg) { Console.WriteLine(msg); } public static void MainD2(string msg) { Console.WriteLine(msg); }
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 9/51
Asiminoaei Ioan
Covarianta si contravarianta in delegates
.NET Framework 3.5 , 4.0 si Visual Studio 2008 au introdus suport pentru
varianta pentru determinarea semnaturii metodelor cu tipurile delegate, adica putem
atribui la delegates nu numai metode ce se potrivesc cu semnatura delegate-ului, dar si
metode ce returneaza tipuri situate mai jos in ierarhia de derivare (covarianta) sau
accepta parametri ce sunt de un tip situat mai sus in ierarhia de derivare (contravarianta)
fata de tipul specificat de delagate.
Aceasta include atat delegates generici cat si delegates non-generici.
Sa urmarim aceste lucruri in exemplele de mai jos.
public class First { } // Clasa Second este situata “mai sus” in ierarhia de derivare public class Second : First { } // Definim doi delegates: unul non-generic si unul generic public delegate First SampleDelegate(Second a); public delegate R SampleGenericDelegate<A, R>(A a);
iar metodele atribuite pot fi:
// Se potriveste cu semnataura public static First ASecondRFirst(Second first) { return new First(); } // Tipul returnat este “mai” derivat. public static Second ASecondRSecond(Second second) { return new Second(); } // Tipul argumentului este mai putin derivat. public static First AFirstRFirst(First first) { return new First(); } // Tipul returnat este “mai” derivat si // tipul argumentului este mai putin derivat public static Second AFirstRSecond(First first) { return new Second(); }
Urmatorul exemplu ilustreaza conversia implicita intre semnataura metodei si tipul
delegate.
// Atribuim o metoda ce se potriveste ca semnatura la // un delegate non-generic. // Nu e necesara conversia. SampleDelegate dNonGeneric = ASecondRFirst; // Atribuim o metoda cu un tip returnat “mai” derivat // si tipul argumentului mai putin derivat la
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 10/51
Asiminoaei Ioan
// un delegate non-generic. // Se foloseste conversia implicita. SampleDelegate dNonGenericConversion = AFirstRSecond; // Atribuim o metoda ce se potriveste ca semnataura // la un delegate generic. // Nu e necesara conversia. SampleGenericDelegate<Second, First> dGeneric = ASecondRFirst; // Atribuim o metoda cu un tip returnat « mai putin » derivat // si tipul argumentului « mai derivat » la // un delegate generic. // Se foloseste conversia implicita. SampleGenericDelegate<Second, First> dGenericConversion = AFirstRSecond;
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 11/51
Asiminoaei Ioan
Delegate - Varianta in parametri de tip generic
In .NET Framework 4, putem realiza conversia implicita intre delegates, astfel
incat delegates generici ce au specificat tipuri diferite ca parametri generici pot fi atribuiti
unul altuia cu conditia ca tipurile sa fie mostenite unul din altul asa cum e cerut de
varianta.
Cuvintele cheie sunt:
� in pentru delegate contravariant;
� out pentru delegate covariant.
// Tipul T este declarat covariant folosind cuvantul cheie out. public delegate T SampleGenericDelegate <out T>(); public static void Test() { SampleGenericDelegate <String> dString = () => " "; // Putem face urmatoarea atribuire deoarece T este marcat // cu out (covariant) si Object este mai jos in ierarhie // decat String SampleGenericDelegate <Object> dObject = dString; }
In exemplul urmator, SampleGenericDelegate<String> nu poate fi convertit in mod
explicit la SampleGenericDelegate<Object>, desi String este derivata din Object.
Problema se rezolva daca marcam parametrul generic T cu out.
public delegate T SampleGenericDelegate<T>(); public static void Test() { SampleGenericDelegate<String> dString = () => " "; // You can assign the dObject delegate // to the same lambda expression as dString delegate // because of the variance support for // matching method signatures with delegate types. SampleGenericDelegate<Object> dObject = () => " "; // The following statement generates a compiler error // because the generic type T is not marked as covariant. // SampleGenericDelegate <Object> dObject = dString; }
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 12/51
Asiminoaei Ioan
Delegates predefiniti in .NET
.NET Framework 4 introduce suport pentru varianta pentru parametri de tip generic in
urmatorii delegates:
• Delegate Action din namespace System, de exemplu, Action <T > si Action <T1, T2 >
• Delegates Func din namespace System, de exemplu, Func <TResult > si Func <T, TResult >
• Delegate Predicate <T >
• Delegate Comparison <T >
• Delegate Converter <TInput, TOutput >
Daca un delegate generic are parametri generici covarianti sau contravarianti, poate fi
referit ca un delegate generic variant.
Exemplu delegate covariant:
public delegate R DCovariant<out R>();
Exemplu delegate contravariant:
public delegate void DContravariant<in A>(A a);
Exemplu delegate covariant si contravariant
public delegate R DVariant<in A, out R>(A a);
Exemplu : cast de la clasa de baza la clasa derivata
In cadrul unei ierarhii de clase construim un singur delegate ce va puncta la metode ce
returneaza tipuri de clase conform regulilor de mostenire a claselor.
Consideram clasa B derivata din A.
class A {} class B : A {}
iar in clasa de test putem scrie :
// Definim un delegate ce va puncta la o metoda fara parametri // si care returneaza o instanta a tipului A. public delegate A ObtinA();
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 13/51
Asiminoaei Ioan
public static A GetA() {
return new A(); } public static B GetB() {
return new B(); } static void Main(string[] args) {
Console.WriteLine("***** Covarianta Delegate *****\n"); ObtinA targetA = new ObtinA(GetA); A a = targetA(); Console.WriteLine("Am obtinut {0}", a);
// Covariance permite aceasta atribuire. // Atentie! ObtinA returneaza un obiect de tip A // Il folosim aici pt a obtine o instanta a clasei B ObtinA targetB = new ObtinA(GetB); B b = (B)targetB(); Console.WriteLine("Am obtinut {0}", b);
}
Un exemplu complet cu varianta delegates bazat pe o ierarhie de clase
Consideram urmatoarea ierarhie de clase.
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Delegate { class Persoana { } class Client : Persoana { } class Salariat : Persoana { } class Manager : Salariat{ }
}
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 14/51
Asiminoaei Ioan
PersoanaClass
Client
Persoana
Class
Salariat
Persoana
Class
Manager
Salariat
Class
In clasa de test avem urmatorul cod :
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Delegate { class Program { delegate T Actiune<T>(); static void Main(string[] args) { // incercare covarianta Actiune<Client> getClient = () => new Client();
// Linia urmatoare nu se compileaza
Actiune<Persoana> getPersoana = getClient; } } }
Rezolvare: se declara delegate astfe (covariant)l:
delegate T Actiune<out T>();
Acum getPersoana va returna un Client care este derivat din Persoana.
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 15/51
Asiminoaei Ioan
Covarianta (out) si contravarianta (in) using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Delegate { class Program { delegate T Actiune<out T>(); // Delegate pentru demo contravarianta // Tipul T nu e declarat cu in, deci contravarianta
// nu va functiona delegate void ActiuneContravarianta<T>(T t); static void Main(string[] args) { // incercare covarianta Actiune<Client> getClient = () => new Client(); Actiune<Persoana> getPersoana = getClient; // incercare contravarianta ActiuneContravarianta<Persoana> printPersoana = (persoana) => Console.WriteLine(persoana); // Nu se compileaza ActiuneContravarianta<Client> printClient =
printPersoana; Console.ReadLine(); } } }
Varianta corecta: delegate void ActiuneContravarianta<in T>(T t);
Codul complet si corect este dat in continuare. using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Delegate { class Program
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 16/51
Asiminoaei Ioan
{ delegate T Actiune<out T>(); delegate void ActiuneContravarianta<in T>(T t); static void Main(string[] args) { // incercare covarianta Actiune<Client> getClient = () => new Client(); Actiune<Persoana> getPersoana = getClient; // incercare contravarianta ActiuneContravarianta<Persoana> printPersoana =
(persoana) => Console.WriteLine(persoana);
// Se compileaza acum ActiuneContravarianta<Client> printClient =
printPersoana; Console.ReadLine(); } } }
Daca vom incerca in Main ceva de genul:
ActiuneContravarianta<Salariat> printSalariat =
(salariat) => Console.WriteLine(salariat); ActiuneContravarianta<Client> printClient2 = printSalariat;
nu va functiona deoarece Client nu este derivat din Salariat.
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 17/51
Asiminoaei Ioan
Liste de delegates (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 gestionarea
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 value’s // 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 :
class A { public delegate void D1(string msg); public delegate void D2(string msg); private D1 d1_delegat; private D2 d2_delegat; public void OnD1(D1 metodaClient)
{ d1_delegat += metodaClient; // Combine } public void OnD2(D2 metodaClient) {
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 18/51
Asiminoaei Ioan
d2_delegat += metodaClient; // Combine } public void ApelDelegates() { ... if (d1_delegat != null) d1_delegat("D1!"); ... if (d2_delegat != null) d2_delegat("D2"); }
} in Main A a = new A(); a.OnD1(new A.D1(MainD1)); a.OnD2(new A.D2(MainD2)); a.OnD1(new A.D1(MainD1_1)); a.ApelDelegates();
si metodele MainD1, MainD2 – in aceeasi clasa cu metoda Main public static void MainD1(string msg) { Console.WriteLine(msg); } public static void MainD1_1(string msg) { Console.WriteLine(msg); } public static void MainD2(string msg) { Console.WriteLine(msg); }
Operatorul += este sinonim cu Combine, iar -= este sinonim cu Remove.
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 19/51
Asiminoaei Ioan
Mai mult control asupra metodei Invoke
Algoritmul de executie al metodleor callback este unul “serial”.
In momentul cand se folosesc “lanturi” de delegates, valoarea returnata este data de
ultimul delegate ce se executa. Acest lucru poate constitui in unele cazuri o problema.
O alta situatie, in cazul « lanturilor » de delegates, este cea data de generarea unei
exceptii sau executia unei metode “lente” in cazul unei metode callback.
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. In fapt aceasta metoda returneaza o colectie de
delegates, colectie pe care o iteram si executam fiecare delegate din colectie.
Metoda este definita astfel:
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 de folosire
delegate String GetStatus(); //
// Delegate[] arrayOfDelegates = // status.GetInvocationList(); // Declar o lista vida de delegate. GetStatus getStatus = null;
//Construiesc lista de delegates
getStatus += new GetStatus(new A().MetodaA); getStatus += new GetStatus(new B().MetodaB); getStatus += new GetStatus(new C().MetodaC); string results = GetResults(getStatus);
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 20/51
Asiminoaei Ioan
Construiesc functia ce apeleaza aceasta lista de delegates si returneaza rezultatele. static String GetResults(GetStatus status) {
if (status == null) return null; StringBuilder res = new StringBuilder();
// Obtinem lista de delegates
Delegate[] ad = status.GetInvocationList();
// Iteram pe fiecare delegate din lista foreach (GetStatus getStatus in ad) {
try {
//Construim rezultatul
res.AppendFormat("{0}{1}{1}", getStatus(), Environment.NewLine);
} catch (Exception e) {
// exceptie res.AppendFormat(“Exceptie...” +
e.Message); }
} // Returnam rezultatul
return res.ToString();
}
Observatie
Folosind aceasta tehnica putem controla fiecare delegate si putem “reactiona” (scriind
cod) pentru a trata exceptiile.
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 21/51
Asiminoaei Ioan
Delegate si Reflection
In metodele callback discutate pana 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 (va fi apelata metoda asociata acestui eveniment), altfel nu.
Metodele folosite sunt CreateDelegate si DynamicInvoke.
Metodele din clasa delegate ce realizeaza acest lucru sunt (consultati MSDN):
Name Description
CreateDelegate(Type, MethodInfo)
Creates a delegate of the specified type to represent the
specified static method.
CreateDelegate(Type, Object, MethodInfo)
Creates a delegate of the specified type that represents
the specified static or instance method, with the
specified first argument.
Starting with the .NET Framework version 2.0 Service
Pack 1, this method can be used to access non-public
methods if the caller has been granted
ReflectionPermission with the ReflectionPermissionFlag
.RestrictedMemberAccess flag and if the grant set of the
non-public methods is restricted to the caller’s grant set,
or a subset thereof. (See Security Considerations for
Reflection.)
To use this functionality, your application should target
the .NET Framework version 3.5 or later.
CreateDelegate(Type, Object, String)
Creates a delegate of the specified type that represents
the specified instance method to invoke on the specified
class instance.
CreateDelegate(Type, MethodInfo, Boolean)
Creates a delegate of the specified type to represent the
specified static method, with the specified behavior on
failure to bind.
CreateDelegate(Type, Type, String)
Creates a delegate of the specified type that represents
the specified static method of the specified class.
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 22/51
Asiminoaei Ioan
CreateDelegate(Type, Object, MethodInfo, Boolean)
Creates a delegate of the specified type that represents
the specified static or instance method, with the
specified first argument and the specified behavior on
failure to bind.
CreateDelegate(Type, Object, String, Boolean)
Creates a delegate of the specified type that represents
the specified instance method to invoke on the specified
class instance with the specified case-sensitivity.
CreateDelegate(Type, Type, String, Boolean)
Creates a delegate of the specified type that represents
the specified static method of the specified class, with
the specified case-sensitivity.
CreateDelegate(Type, Object, String, Boolean, Boolean)
Creates a delegate of the specified type that represents
the specified instance method to invoke on the specified
class instance, with the specified case-sensitivity and the
specified behavior on failure to bind.
CreateDelegate(Type, Type, String, Boolean, Boolean)
Creates a delegate of the specified type that represents
the specified static method of the specified class, with
the specified case-sensitivity and the specified behavior
on failure to bind.
si in final
public class Delegate {
// Invocare metoda callback a obiectului delegate, // pasindu-i parametrii la runtime public Object DynamicInvoke(Object[] args);
}
Toate metodele CreateDelegate construiesc un obiect nou de tip Delegate,
identificat de primul parametru de tip Type.
Ceilalti parametri determina metoda callback pe care obiectul derivat din Delegate o va
implementa precum si informatii auxiliare.
Metoda DynamicInvoke ne permite sa invocam o metoda callback a obiectului
delegate, pasindu-i parametrii la runtime.
DynamicInvoke face un control al parametrilor (acelasi numar, acelasi tip, aceeasi
pozitie) pentru metoda callback invocata. In caz de esec se genereaza o exceptie.
DynamicInvoke returneaza obiectul returnat de metoda callback.
Exemplu (MSDN) using System; using System.Reflection; using System.Security.Permissions;
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 23/51
Asiminoaei Ioan
// Declare three delegate types for demonstrating // the combinations of static versus // instance methods and open versus closed delegates. // public delegate void D1(C c, string s); public delegate void D2(string s); public delegate void D3(); // // A sample class with an instance method and a static method. // public class C { private int id; public C(int id) { this.id = id; } public void M1(string s) { Console.WriteLine("Instance method M1 on C: id = {0}, s = {1}",this.id, s); } public static void M2(string s) { Console.WriteLine("Static method M2 on C: s = {0}", s); } } // End of class C // Clasa de test public class Example { public static void Main() { C c1 = new C(42); // Get a MethodInfo for each method. // metoda a instantei MethodInfo mi1 = typeof(C).GetMethod("M1", BindingFlags.Public | BindingFlags.Instance); // metoda statica MethodInfo mi2 = typeof(C).GetMethod("M2", BindingFlags.Public | BindingFlags.Static); D1 d1; // D1(C c, string s) D2 d2; // D2(string s) D3 d3; // D3()
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 24/51
Asiminoaei Ioan
Console.WriteLine("\nAn instance method closed over C."); // Trebuie sa furnizam instanta clasei ce contine // metoda callback // In this case, the delegate and the // method must have the same list of argument types; use // delegate type D2 with instance method M1. // Delegate test = Delegate.CreateDelegate(typeof(D2), c1, mi1, false); // Because false was specified for throwOnBindFailure // in the call to CreateDelegate, the variable 'test' // contains null if the method fails to bind (for // example, if mi1 happened to represent a method of // some class other than C). // if (test != null) { d2 = (D2)test; // atribuire metoda callback // The same instance of C is used every time the // delegate is invoked. d2("Hello, World!"); d2("Hi, Mom!"); } Console.WriteLine("\nAn open instance method."); // Cream delegate fara a furniza instanta clasei // ce contine metoda callback. Instanta clasei o // vom furniza in momentul apelului metodei callback. // In this case, the delegate has one more // argument than the instance method; this argument comes // at the beginning, and represents the hidden instance // argument of the instance method. Use delegate type D1 // with instance method M1. // d1 = (D1) Delegate.CreateDelegate(typeof(D1), null, mi1); // An instance of C must be passed in each time the // delegate is invoked. // d1(c1, "Hello, World!"); d1(new C(5280), "Hi, Mom!"); Console.WriteLine("\nAn open static method.");
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 25/51
Asiminoaei Ioan
// In this case, the delegate and the method must // have the same list of argument types; // use delegate type D2 with static method M2. // d2 = (D2) Delegate.CreateDelegate(typeof(D2), null, mi2); // No instances of C are involved, // because this is a static method. // d2("Hello, World!"); d2("Hi, Mom!"); Console.WriteLine("\nA static method closed “ + “ over the first argument (String)."); // The delegate must omit the first argument // of the method. // A string is passed as the firstArgument parameter, and // the delegate is bound to this string. // Use delegate type D3 with static method M2. // d3 = (D3) Delegate.CreateDelegate(typeof(D3), "Hello, World!", mi2); // Each time the delegate is invoked, the same string is // used. d3(); } }
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 26/51
Asiminoaei Ioan
Creare delegates generici
Ideea este de a construi delegates generici si pe baza acestora sa definim un delegate
concret.
Studiu de caz
Un delegate ce mapeaza o functie ce are un singur parametru si returneaza void.
Observatie
Vedeti si alte situatii date mai jos ca exemplu.
Daca parametrul este diferit de la metoda la metoda putem construi urmatorul delegate
generic :
namespace GenericDelegate {
public delegate void GenericDelegate<T>(T arg); class Program {
static void Main(string[] args) {
Console.WriteLine("***** Generic Delegates *****\n"); // Specificam o metoda ce are ca parametru un string GenericDelegate<string> strTarget = new GenericDelegate<string>(StringTarget); strTarget("Some string data"); // Specificam o metoda ce are ca parametru un int GenericDelegate<int> intTarget = new MyGenericDelegate<int>(IntTarget); intTarget(9); Console.ReadLine();
} static void StringTarget(string arg) {
Console.WriteLine("arg in uppercase is: {0}", arg.ToUpper());
} static void IntTarget(int arg) {
Console.WriteLine("++arg is: {0}", ++arg); } }
}
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 27/51
Asiminoaei Ioan
Mai multe exemple:
namespace DelegateAsincronAndGeneric {
public delegate void ActiuneDelegate<T>(T value);
public delegate T DelegateT<T>(T t); public delegate U DelegateTU<T, U>(T t); class CreateDelegate { public ActiuneDelegate<string> md; public DelegateT<string> dt; public DelegateTU<string, int> udt; public int MIntStr(string str) { return str.Length; } public string MetodaGenericDelegate(string str) { return "Modificat " + str; } public void Metoda(string str) { Console.WriteLine("CreateDelegate.Metoda {0}", str); } // nu e necesar sa punem in ctor // E numai in scop demonstrativ // public CreateDelegate(string str) { md = this.Metoda; dt = this.MetodaGenericDelegate; udt = this.MIntStr; } } }
In clasa de test putem scrie:
using System; using System.Collections.Generic; using System.Linq; using System.Text;
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 28/51
Asiminoaei Ioan
namespace DelegateAsincronAndGeneric { class Program { static void Main(string[] args) { CreateDelegate cd = new CreateDelegate("Info"); // // Schimbam metoda callback pt ActiuneDelegate //
cd.md = (str) => { Console.WriteLine("Delegate Lambda: " +str); };
cd.md("Iasi"); // // Pentru DelegateTU atasam metoda MIntStr // Este metoda a instantei deci avem nevoie de // instanta tipului unde este definita metoda
Program p = new Program();
cd.udt = p.MIntStr; // // Pentru DelegateT metoda este cea furnizata in clasa // CreateDelegate si atasata in ctor // Console.WriteLine(
cd.dt("Vaslui") + " Length = " + cd.udt("Vaslui"));
} public int MIntStr(string str) { return str.Length + 20; } } }
si un delegate cu doi parametri generici:
public delegate DelegateEventHandler<T,U>(T sender, U eventArgs) ;
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 29/51
Asiminoaei Ioan
Simulare delegates generici fara a folosi generic
Putem face acest lucru prin a crea un delegate ce mapeaza o functie ce are un parametru
de tip Object. In acest caz exista boxing si unboxing.
public delegate void NonGenericDelegate(object arg);
class Program {
static void Main(string[] args) {
... NonGenericDelegate d =
new NonGenericDelegate(MyTarget); d("More string data"); NonGenericDelegate d2 = MyTarget; d2(9); // Penalizare boxing. Console.ReadLine();
} // Trebuie sa facem conversie la tipul pe care il asteptam static void MyTarget(object arg) {
if(arg is int) {
int i = (int)arg; // Penalizare unboxing. Console.WriteLine("++arg is: {0}", ++i);
} if(arg is string) {
string s = (string)arg; // Nu avem unboxing Console.WriteLine("arg in uppercase is: {0}", s.ToUpper());
} }
}
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 30/51
Asiminoaei Ioan
Evenimente
Membrul event al unei clase permite acesteia de a notifica alte obiecte ca ceva s-a
intamplat.
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.
Exemplu de declarare event :
// declarare delegate pentru gestionare eveniment click delegate void ButtonEventHandler(object source,int clickCount);
// declaram evenimentul public event ButtonEventHandler ButtonClick;
Modelul eveniment al CLR-ului este bazat pe delegates.
Exemplu
Consideram clasa Button pentru care dorim sa tratam mesajul generat de evenimentul clic
pe acesta. using System; // declarare delegate pentru gestionare eveniment click delegate void ButtonEventHandler(object source,int clickCount); class Button { // declaram evenimentul public event ButtonEventHandler ButtonClick; //Functia ce genereaza evenimentul public void Clicked(int count) { Console.WriteLine("\nInside Clicked !!!"); //Invocarea tuturor metodelor atasate pentru eveniment if (ButtonClick != null) ButtonClick(this,count); } }
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 31/51
Asiminoaei Ioan
// In clasa Dialog se foloseste tipul Button public class Dialog { public Dialog() { Button b = new Button(); // Adaugam metoda pentru tratatre eveniment b. ButtonClick += new ButtonEventHandler( OnButtonAction); //Generare eveniment b.Clicked(1); b.ButtonClick += new ButtonEventHandler(OnButtonAction); b.Clicked(1); //Eliminare metoda ce trateaza evenimentul b.ButtonClick -= new ButtonEventHandler(onButtonAction); b.Clicked(1); b.ButtonClick -= new ButtonEventHandler(onButtonAction); b.Clicked(1); } // Clasa de test static void Main() { new Dialog(); } // Functia (metoda) pentru tratarea evenimentului public void onButtonAction(object source,int clickCount) { Console.WriteLine("Inside Event Handler !!!"); } }
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 32/51
Asiminoaei Ioan
Definirea evenimentelor cu MulticastDelegates
Pattern-ul Publish / Subscribe (Event)
Clasa din FCL este MulticastDelegates.
O instanta a acestei clase reprezinta un delegate ce are mai multe elemente in lista de
invocare.
Compilatorul poate deriva din aceasta clasa, dar noi in cod nu putem face explicit acest
lucru. La fel si pentru clasa Delegate.
CLR furnizeaza doua metode speciale: BeginInvoke si EndInvoke folosite in apelul
asincron.
Observatie
Cand este invocat un delegate de acest tip (multicast), delegates din lista de invocare sunt
apelati in mod sincron in ordinea in care apar in lista. Daca apare o eroare in timpul
executiei listei de delegates se genereaza o exceptie.
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 pattern-ul 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 de tip EventArgs sau derivat din EventArgs.
3. Al doilea parametru trebuie sa fie derivat din clasa EventArgs.
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 33/51
Asiminoaei Ioan
Descriere pattern Publish / Subscribe sau pattern Event
Modelul logic al patern-ului observer implica doua entitati : Observer si Subiect.
Observer : responsabil pentru « afisarea » datelor catre utilizator.
Subiect : reprezinta modelul problemei. Cand apare o modificare in Subiect, Observer-ul
observa aceasta modificare si executa actiunea « programata ».
Modelul fizic
Observer :
Observer-ul isi exprima interesul fata de Subiect pentru anumite notificari, deci acesta se
inregistreaza (Register / Attach) la Subiect cu o anumita cerinta.
Observer-ul trebuie sa aiba si posibilitatea renuntarii de a fi notificat pentru o anumita
cerinta – (Unregister / Detach).
Observer-ul instiinteaza Subiect despre metoda ce va trebui apelata cand apare
evenimentul monitorizat (OnNotify).
Subiect :
Inregistreaza / elimina observerii interesati de un anumit eveniment ; uzual metodele
Register / Attach, Unregister / Detach).
Notifica observer despre aparitia evenimentului (Notify).
.NET furnizeaza urmatoarele interfete pentru acest pattern :
1. Interfata IObserver<T> public interface IObserver<in T>
Interfata IObserver<T> reprezinta clasa ce primeste notificari. T reprezinta clasa ce
furnizeaza informatia de notificare. Furnizorul informatiei este dat de clasa ce
implementeaza interfata IObservable<T>.
Interfata IObserver<T> defineste urmatoarele metode pe care observerul trebuie sa le
implementze :
• Metoda OnNext, ce este apelata de “provider” pentru a furniza informatii catre
observer.
• Metoda OnError, apelata de “provider” pentru a indica ca informatia nu este
disponibila, au aparut anumite erori.
• Metoda OnCompleted, apelata de provider pentru a semnala ca a terminat
trimiterea notificarilor la observeri.
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 34/51
Asiminoaei Ioan
public class Observer : IObserver<Tip_clasa_struct> { public void OnNext(Tip_clasa_struct value) { } public void OnError(Exception error) { } public void OnCompleted() { } }
2. Interfata IObservable<T> : furnizeaza informatia de notificare
public interface IObservable<out T>
T trebuie sa fie acelasi in IObserver si IObservable.
Provider-ul trebuie sa implementeze o singura metoda, Subscribe ce indica faptul ca un
observer doreste sa fie notificat. Apelantul acestei metode trebuie sa paseze o instanta a
observerului.
Exemplu (MSDN): public enum LocationStatus { Started = 1, EnRoute = 2, Finished = 3 }; public struct Location { public readonly decimal Latitude; public readonly decimal Longitude; public readonly DateTime DateAndTime; public readonly LocationStatus Status; public Location(decimal lat, decimal lon, DateTime dateAndTime, LocationStatus status) { this.Latitude = lat; this.Longitude = lon; this.DateAndTime = dateAndTime; this.Status = status; } }
public class LocationSimulator : IObservable<Location> { List<IObserver<Location>> observers = new List<IObserver<Location>>(); Location _location, _lastLocation, _startLocation;
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 35/51
Asiminoaei Ioan
public static LocationSimulator SetStartingLocation() { return new LocationSimulator(42.2857m, -83.7213m, LocationStatus.Started); } private LocationSimulator(decimal latitude, decimal longitude, LocationStatus status) { _location = new Location(latitude, longitude, DateTime.UtcNow, status); _lastLocation = _location; if (status == LocationStatus.Started) _startLocation = _location; } public Location Location { get { return this._location; } } public Location GetCurrentLocation() { Random rnd = new Random(); decimal newLat = _location.Latitude + rnd.Next(-1, 2); decimal newLong = _location.Longitude + rnd.Next(-1, 2); // Assume arrival if the difference in latitude is 3. _lastLocation = _location; LocationStatus status = Math.Abs(_startLocation.Latitude – newLat) >= 3 ? LocationStatus.Finished : LocationStatus.EnRoute; _location = new Location(_location.Latitude + rnd.Next(-1, 2), _location.Longitude + rnd.Next(-1, 2), DateTime.UtcNow, status); if (! _location.Equals(_lastLocation)) { // Notify observers. foreach (IObserver<Location> observer in observers) { observer.OnNext(this.Location); // Assume that we've arrived at location of // Latitude has changed by 4. if (_location.Status == LocationStatus.Finished) observer.OnCompleted(); } } return this.Location; } public IDisposable Subscribe(IObserver<Location> observer) { observers.Add(observer); // Announce current location to new observer. observer.OnNext(this.Location); return observer as IDisposable; } }
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 36/51
Asiminoaei Ioan
public class LocationDisplay : IObserver<Location> { public void OnNext(Location value) { Console.WriteLine("{3}At {0}, Latitude = {1:N4}, Longitude = {2:N4}", value.DateAndTime, value.Latitude, value.Longitude, value.Status == LocationStatus.Started ? "Starting " : ""); } public void OnError(Exception error) { Console.WriteLine("Unable to determine the current location."); } public void OnCompleted() { Console.WriteLine("Finished tracking the current location."); } }
public class Example { public static void Main() { LocationSimulator simulator = LocationSimulator.SetStartingLocation(); // Subscribe with class that implements IObserver<Location> IDisposable d = simulator.Subscribe(new LocationDisplay()); Location loc; do { loc = simulator.GetCurrentLocation(); Thread.Sleep(2500); } while (loc.Status != LocationStatus.Finished); } }
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 37/51
Asiminoaei Ioan
Pattern Observer folosind delegates
Exemplu:
public class Info {
// delegate pentru eveniment public delegate void InfoDelegate(object obj); // event ce foloseste delegate InfoDelegate public event InfoDelegate InfoChanged; // data membru object _obiect; public object SetInfo { set
{ _obiect = value; // genereaza eveniment InfoChanged(_obiect); }
} } public class InfoDisplay { public void InfoChanged(object obj)
{ Console.WriteLine(“Info changed”); }
} public class Test { public static void Main()
{ InfoDisplay infodisplay = new InfoDisplay();
Info info = new Info(); // inregistrare metoda callback pentru event
Info.InfoDelegate del = new Info.InfoDelegate(InfoDisplay.InfoChanged); info.InfoChanged += del; // schimbare stare in Info info.SetInfo = "changed"; // unregister info.InfoChanged -= del;} }
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 38/51
Asiminoaei Ioan
Pattern Publish / Subscribe sau Event - implementare
Schema generala a cestui pattern este data in urmatorul cod (partial MSDN).
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace EventPattern { public class Stoc { // declar delegate pt eveniment public delegate void AskPriceChangedHandler(object sender,
AskPriceChangedEventArgs e); // declar event public event AskPriceChangedHandler AskPriceChanged; // data membru object _askPrice; // proprietate public object AskPrice { set { _askPrice = value; // generare event OnAskPriceChanged(); } } // metoda ce genereaza evenimentul protected void OnAskPriceChanged() { AskPriceChanged(this,
new AskPriceChangedEventArgs(_askPrice)); } } // clasa Stoc // parametrii evenimentului public class AskPriceChangedEventArgs : EventArgs { private object _askPrice; // ctor public AskPriceChangedEventArgs(object askPrice) { _askPrice = askPrice; } public object AskPrice { get {
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 39/51
Asiminoaei Ioan
return _askPrice; } } } // AskPriceChangedEventArgs }
iar clasa de test poate fi urmatoarea:
class MainStoc { static void Main(string[] args) { Stoc s = new Stoc(); s.AskPriceChanged +=new Stoc.AskPriceChangedHandler(s_AskPriceChanged); s.AskPrice = 100; } static void s_AskPriceChanged(object sender, AskPriceChangedEventArgs e) { Console.WriteLine("Stoc display: {0}", e.AskPrice); //throw new NotImplementedException(); } }
Exemplu din Inside C# Second Edition – Tom Archer
Observati diferenta la constructori.
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.
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 40/51
Asiminoaei Ioan
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)
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 {
// Tipul MailMsgEventArgs este definit in interiorul tipului // MailManager
public class MailMsgEventArgs : EventArgs {
public readonly String from, to, subject, body; // 1. Parametrul evenimentului public MailMsgEventArgs(
String from, String to, String subject, String body)
{ this.from = from;
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 41/51
Asiminoaei Ioan
this.to = to; this.subject = subject; this.body = body; }
}
// 2. Tipul delegate – prototipul pentru callback // Metoda trebuie implementata de clienti public delegate void MailMsgEventHandler(
Object sender, MailMsgEventArgs args); // 3. Evenimentul public event MailMsgEventHandler MailMsg; // 4. Metoda responsabila pentru notificarea obiectelor // inregistrate pentru acest eveniment protected virtual void OnMailMsg(MailMsgEventArgs e) {
if (MailMsg != null) {
MailMsg(this, e); }
}
// 5. Metoda este apelata cand apare un mesaj e-mail public void SimulateArrivingMsg(String from, String to,
String subject, String body) {
// Construim parametrul evenimentului MailMsgEventArgs e = new MailMsgEventArgs(from, to, subject, body); // Notificam clientul ca a aparut un e-mail 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).
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 42/51
Asiminoaei Ioan
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: 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. Referinta la inceputul listei de delegates
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 43/51
Asiminoaei Ioan
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.
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 44/51
Asiminoaei Ioan
Proiectarea unui tip ce asteapta pentru un eveniment
Exemplu class Fax {
// Pass the MailManager object to the constructor. public Fax(MailManager mm) {
// Indicam metoda ce va fi apelata cand soseste un e-mail mm.MailMsg+= new MailManager.MailMsgEventHandler(FaxMsg);
} // Aceasta este metoda pe care MailManager o va apela pentru // a notifica obiectul Fax ca a sosit un nou e-mail. private void FaxMsg(
Object sender, MailManager.MailMsgEventArgs e) {
// ‘sender’ identifica MailManager 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 MailManager’s 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).
In continuare obiectul Fax se inregistreaza pentru evenimentul MailMsg din
mailManager :
mm.MailMsg += new MailManager.MailMsgEventHandler(FaxMsg);
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 45/51
Asiminoaei Ioan
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.
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 46/51
Asiminoaei Ioan
Delegates asincroni
Apelul asincron al delegates presupune crearea de catre framework a unui fir de lucru din
ThreadPool. Din acest motiv apelul nu este blocant.
Este posibil ca metoda apelata sa genereze date si sa le returneze. In acest caz exista doua
posibilitati oferite de infrastructura .NET :
� apelantul asteapta explicit aceste date – apel metoda EndInvoke ;
� apelantul este instiintat de apelat ca executia s-a terminat .
Clasa System.MulticastDelegates contine printre altele si metodele IAsyncResult BeginInvoke(...,
AsyncCallback asc, object stateObject);
EndInvoke(..., IAsyncResult);
folosite in apelul asincron al delegates-lor.
Observam ca aceste metode returneaza sau au ca parametru un tip IAsyncResult, care
e definit ca o interfata.
• IAsyncResult reprezinta statusul unei operatii asincrone.
• IAsyncResult este implementata de clase ce contin metode ce pot opera in mod
asincron.
• IAsyncResult este tipul returnat de o metoda ce initiaza o operatie asincrona si
este un parametru al metodei ce verifica terminarea operatiei asincrone (in mod
normal metode ce incep cu Begin... si End...).
• Un obiect de tip IAsyncResult este pasat metodelor invocate de delegates
AsyncCallback cand o operatie asincrona s-a terminat de executat.
• Un obiect ce suporta interfata IAsyncResult memoreaza informatie de stare
pentru o operatie asincrona si furnizeaza un obiect de sincronizare ce permite
firelor sa fie semnalate cand operatia s-a terminat.
Clasa AsyncResult este implementarea interfetei IAsyncResult ce este returnata de
metoda BeginInvoke cand folosim un delegate pentru a apela o metoda in mod
asincron.
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 47/51
Asiminoaei Ioan
public interface IAsyncResult { // Furnizeaza un obiect definit de utilizator
// sau contine informatii despre o operatie asincrona
object AsyncState { get; }
// Returneaza un WaitHandle folosit pentru a astepta // terminarea unei operatii asincrone. // Valoarea returnata poate fi folosita pentru // a executa operatii WaitOne, WaitAny sau WaitAll
WaitHandle AsyncWaitHandle { get; }
// Returneaza o valoare ce indica daca operatia
// asincrona s-a terminat in mod sincron
bool CompletedSynchronously { get; } // Returneaza o valoare ce indica daca operatia
// asincrona a fost terminata bool IsCompleted { get; }
}
Clasa AsyncResult
Incapsuleaza rezultatele unei operatiuni asincrone pe un delegate.
Namespace: System.Runtime.Remoting.Messaging
Assembly: mscorlib (in mscorlib.dll)
[ComVisibleAttribute(true)] public class AsyncResult : IAsyncResult, IMessageSink
Observatie
IAsyncResult returnat de metoda BeginInvoke poate fi convertit (cast) la
AsyncResult. AsyncResult are proprietatea AsyncDelegate ce mentine obiectul
delegate pe care a fost facut apelul asincron.
Metoda BeginInvoke este folosita pentru a initia un apel asincron. Are aceeasi
parametri ca metoda pe care dorim sa o executam asincron, plus alti doi parametri ce
specifica metoda ce se va apela cand se termina BeginInvoke si un obiect ce transmite
informatii aditionale.
EndInvoke (metoda blocanta) este folosita pentru a regasi rezultatele unui apel asincron.
Poate fi apelata dupa apelul lui BeginInvoke.
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 48/51
Asiminoaei Ioan
Dupa ce am apelat BeginInvoke putem:
� Sa executam alt cod si apoi sa apelam EndInvoke pentru a obtine rezultatul.
� Sa obtinem un Waithandle folosind IAsyncResult.AsyncWaitHandle, si sa
folosim metoda WaitOne pentru a bloca executia pana cand WaitHandle este
semnalat, si apoi sa apelam EndInvoke.
� Sa indicam o metoda callback (in BeginInvoke), metoda ce va fi apelata cand se
termina executia lui BeginInvoke, si putem apela EndInvoke.
Exemplu
1. Instantiem un delegate ce contine metoda pe care dorim sa o rulam in paralel (in mod
obisnuit unul din delegates predefiniti Func).
2. Apelam BeginInvoke pe delegate, salvand valoarea de retur de tip IAsyncResult.
BeginInvoke returneaza imediat catre apelant, deci putem executa alt cod in continuare.
3. Cand avem nevoie de rezultat, apelam EndInvoke pe delegate, pasand ca valoare
obiectul de tip IAsyncResult, salvat anterior.
static void Main() {
// string – tipul parametrului din metoda Work // int – tipul returnat de metoda Work
Func<string, int> method = Work; IAsyncResult cookie = method.BeginInvoke ("test",
null, null); // // ... cod ce poate fi executat dupa apelul BeginInvoke // // asteptam terminarea executiei metodei Work // si preluam rezultatul int result = method.EndInvoke (cookie); Console.WriteLine ("String length is: " + result);
} static int Work (string s) {
return s.Length; }
Cazul cand penultimul parametru din BeginInvoke este completat, se indica metoda ce
va fi apelata cand s-au terminat calculele.
static void Main() {
Func<string, int> metoda = Work; method.BeginInvoke ("test", Done, metoda);
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 49/51
Asiminoaei Ioan
// ... //
} static int Work (string s) { return s.Length; } static void Done (IAsyncResult cookie) {
// ultimul parametru din BeginInvoke este de tip delegate var target = (Func<string, int>) cookie.AsyncState; int result = target.EndInvoke (cookie); Console.WriteLine ("String length is: " + result);
}
Varianta cand obtinem delegate din AsyncResult (namespace System.Runtime.Remoting.Messaging)
Cod in Main … // delegate de forma Func<int,int,int> add = Add; IAsyncResult iasr = add.BeginInvoke(20, 30, Done, "Gata");
// alta varianta // IAsyncResult iasr = add.BeginInvoke(20, 30, // new AsyncCallback(Done),, "Gata");
…
Metoda Add
static int Add(int x, int y) { Console.WriteLine("\nAdd runing...\n"); return x + y; }
Metoda Done static void Done (IAsyncResult cookie) { // obtinem delegate pentru a putea apela EndInvoke AsyncResult ar = (AsyncResult)cookie; var target = (Func<int,int,int>)ar.AsyncDelegate;
int result = target.EndInvoke (cookie); // parametrul pasat metodei Done string dummy = (string)cookie.AsyncState; Console.WriteLine ("Done apelata cu {0} si “ +
« result este = {1} ", dummy, result);
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 50/51
Asiminoaei Ioan
}
Putem sa ne imaginam urmatoarea schema pentru apelul asincron al delegates (Richard
Grimes):
Numerele din paranteze arata ordinea de executie data de framework.
Sa examinam urmatorul cod : public delegate int CalledDelegate(ref int i); public class Called { public int Time2(ref int i) { i = i * 2; return i; } }
in clasa de test scriem urmatorul cod : Console.WriteLine("\nApel asincron delegates\n"); Called c = new Called();
Apelantul (fir apelant)
Called c = new Called();
CalledDelegate d;
d = new CalledDelegate(c.time2);
IAsyncResult ar;
(1) ar = d.BeginInvoke(42, null, null);
// cod
(3) ar.AsyncWaitHandle.WaitOne();
(5) int res = d.EndInvoke();
CalledDelegate
Metoda = Called.time2;
Target = c;
Rezultat din metoda
Called
int time2(int i)
{ return i *2;}
AsyncThread
2
4
AsyncResult AsyncWaitHandle
Delegates
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 24.11.2014 51/51
Asiminoaei Ioan
CalledDelegate d;
// atasare metoda callback d = new CalledDelegate(c.Time2); IAsyncResult ar; int n = 42; // se creaza firul si obiectul de sincronizare // se lanseaza firul
ar = d.BeginInvoke(ref n, null, null); // cod ce se executa in paralel cu delegate asincron Console.WriteLine("cod n = {0}", n); // asteptare terminare delegate ar.AsyncWaitHandle.WaitOne(); // obtinere rezultat int res = d.EndInvoke(ref n,ar); // afisare rezultat Console.WriteLine("Rezultat : {0} si n = {1}",
res, n);
Rezultatul este (observati valoarea lui n nu e modificata inca in instructiunea) : Console.WriteLine("cod n = {0}", n); Apel asincron delegates cod n = 42 Rezultat : 84 si n = 84 Press any key to continue . . .