Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi 04.11.2013 1/54
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
Delegates Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi 04.11.2013 2/54 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 (documentatie Microsoft):
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 04.11.2013 3/54 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 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 04.11.2013 4/54 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 04.11.2013 5/54 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 04.11.2013 6/54 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 04.11.2013 7/54 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 04.11.2013 8/54 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 04.11.2013 9/54 Asiminoaei Ioan Covarianta si contravarianta in .NET
Exemplele pentru varianta se bazeaza in marea majoritate a cazurilor pe MSDN.
Interfete generice variant .
Putem declara parametri de tip generic in interfete ca fiind covariant sau contravariant.
Covarianta permite metodelor din interfata sa poata returna tipuri ce sunt mai jos in ierarhia de derivare decat cele definite de parametrii de tip generic.
Contravarianta permite metodelor din interfata sa aiba tipuri de argumente ce sunt mai sus in ierarhie decat cel specificat prin parametrul generic.
Observatie Clasa de baza se considera mai jos in ierarhia de derivare.
O interfata generica ce are parametri de tip generic covarianti sau contravarianti se numeste variant .
O interfata care nu e nici covarianta nici contravarianta se numeste invarianta.
Interfetele generice variant se declara folosind cuvintele cheie in si out pentru parametrii generici. out : parametru de tip generic covariant; in : parametru de tip generic contravariant; Regula pentru out: Tipul este folosit numai ca tip returnat de metodele din interfata si nu este folosit ca tip al argumentelor metodelor.
Exemplu: interface ICovariant<out R> { // Corect R GetSomething();
// Urmatoarea instructiune genereaza o eroare la compilare.
// void SetSometing(R sampleArg); } Delegates Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi 04.11.2013 10/54 Asiminoaei Ioan
Observatie : Daca avem un delegate generic contravariant ca parametru al metodei, se poate folosi tipul ca parametru generic pentru delegate.
Regula pentru out: Tipul nu este folosit drept constrangere generica pentru metodele din interfata:
interface ICovariant<out R> { // Urmatoarea instructiune genereaza o eroare la compilare // deoarece putem folosi in constrangeri generice numai // tipuri contravariante sau invariante.
// void DoSomething<T>() where T : R; }
in: declarare parametru de tip generic ca fiind contravariant. Poate fi folosit pentru constrangeri generice.
Tipul contravariant poate fi folosit numai ca un tip al argumetelor metodelor si nu ca valoare de retur.
interface IContravariant<in A> { void SetSomething(A sampleArg); void DoSomething<T>() where T : A;
// Urmatoarea instructiune genereaza o eroare la compilare
// A GetSomething(); }
E posibil sa folosim covarianta si contravarianta in aceeasi interfata, dar pentru parametri diferiti (nu e posibil sa avem in si out pentru acelasi parametru).
interface IVariant<out R, in A> { R GetSomething(); void SetSomething(A sampleArg); R GetSetSometings(A sampleArg); Delegates Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi 04.11.2013 11/54 Asiminoaei Ioan }
Exemple:
interface ICovariant<out R> { R GetSomething(); } class SampleImplementation<R> : ICovariant<R> { public R GetSomething() { // Cod ... return default(R); } }
Clasele ce implementeaza interfete variante sunt invariante.
// Interfata este covarianta. ICovariant<Button> ibutton = new SampleImplementation<Button>(); // Object este mai jos in ierarhia de derivare; // este clasa de baza (direct sau indirect) pentru Button. ICovariant<Object> iobj = ibutton;
// Clasa este invarianta (vedeti observatia de mai sus). SampleImplementation<Button> button = new SampleImplementation<Button>();
// Urmatoarea instructiune genereaza o eroare la compilare // deoarece clasele sunt invariante.
// SampleImplementation<Object> obj = button;
Varianta in interfete generice (C#) Suportul pentru varianta permite conversia implicita a claselor ce implementeaza aceste interfete. .NET Framework 4 introduce suport pentru varianta pe interfetele urmatoare. IEnumerable <T> (T is covariant) IEnumerator <T> (T is covariant) IQueryable <T> (T is covariant) IGrouping <TKey, TElement> ( TKey and TElement are covariant) IComparer <T> (T is contravariant) IEqualityComparer <T> (T is contravariant) Delegates Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi 04.11.2013 12/54 Asiminoaei Ioan IComparable <T> (T is contravariant)
Exemplu (MSDN) :
// Ierarhie simpla de clase class BaseClass { } class DerivedClass : BaseClass { }
// Clasa de comparare. // Interfata IEqualityComparer este definita in // System.Collection.Generics astfel // public interface IEqualityComparer<in T> // adica tipul parametrului este contravariant class BaseComparer : IEqualityComparer<BaseClass> { // metode din interfata IEqualityComparer public int GetHashCode(BaseClass baseInstance) { return baseInstance.GetHashCode(); } public bool Equals(BaseClass x, BaseClass y) { return x == y; } } class Program { static void Test() { IEqualityComparer<BaseClass> baseComparer = new BaseComparer();
// Conversie implicita de la IEqualityComparer<BaseClass> // la IEqualityComparer<DerivedClass>.
Delegates Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi 04.11.2013 13/54 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 04.11.2013 14/54 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 04.11.2013 15/54 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>();
// 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>();
// 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 04.11.2013 16/54 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 04.11.2013 17/54 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 04.11.2013 18/54 Asiminoaei Ioan Persoana Class 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>();
// 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 04.11.2013 19/54 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);
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 04.11.2013 20/54 Asiminoaei Ioan { delegate T Actiune<out T>();
nu va functiona deoarece Client nu este derivat din Salariat. Delegates Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi 04.11.2013 21/54 Asiminoaei Ioan Curs 6
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 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 :
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 } Delegates Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi 04.11.2013 22/54 Asiminoaei Ioan
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 04.11.2013 23/54 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.
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.
// 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);
Construiesc functia ce apeleaza aceasta lista de delegates si returneaza rezultatele. Delegates Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi 04.11.2013 24/54 Asiminoaei Ioan
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 04.11.2013 25/54 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 callers 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. 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 Delegates Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi 04.11.2013 26/54 Asiminoaei Ioan 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;
// Declare three delegate types for demonstrating // the combinations of static versus // instance methods and open versus closed delegates. Delegates Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi 04.11.2013 27/54 Asiminoaei Ioan //
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);
Console.WriteLine("\nAn instance method closed over C.");
// Trebuie sa furnizam instanta clasei ce contine // metoda callback Delegates Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi 04.11.2013 28/54 Asiminoaei Ioan // 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.");
// In this case, the delegate and the method must // have the same list of argument types; // use delegate type // D2 with static method M2. // Delegates Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi 04.11.2013 29/54 Asiminoaei Ioan
// 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. //
// Each time the delegate is invoked, the same string is // used. d3(); } }
Delegates Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi 04.11.2013 30/54 Asiminoaei Ioan Creare delegates generici
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 04.11.2013 31/54 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 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.MetGenDel; 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 04.11.2013 32/54 Asiminoaei Ioan
namespace DelegateAsincronAndGeneric { class Program { static void Main(string[] args) { CreateDelegate cd = new CreateDelegate("Info"); // // Schimbam metoda callback pt ActiuneDelegate //
// // 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 04.11.2013 33/54 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) { ... MyDelegate d = new NonGenericDelegate(MyTarget); d("More string data");
// 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 04.11.2013 34/54 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.
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 04.11.2013 35/54 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 04.11.2013 36/54 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 eveniment. 3. Al doilea parametru trebuie sa fie derivat din clasa EventArgs.
Delegates Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi 04.11.2013 37/54 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. public class Observer : IObserver<Tip_clasa_struct> { public void OnNext(Tip_clasa_struct value) { } Delegates Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi 04.11.2013 38/54 Asiminoaei Ioan
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 class LocationSimulator : IObservable<Location> { List<IObserver<Location>> observers = new List<IObserver<Location>>(); Location _location, _lastLocation, _startLocation;
public static LocationSimulator SetStartingLocation() { return new LocationSimulator(42.2857m, -83.7213m, LocationStatus.Started); }
Delegates Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi 04.11.2013 39/54 Asiminoaei Ioan 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; } }
public class LocationDisplay : IObserver<Location> { public void OnNext(Location value) { Delegates Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi 04.11.2013 40/54 Asiminoaei Ioan 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); } }
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 Delegates Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi 04.11.2013 41/54 Asiminoaei Ioan { _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; } }
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, Delegates Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi 04.11.2013 42/54 Asiminoaei Ioan 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 { 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); Delegates Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi 04.11.2013 43/54 Asiminoaei Ioan s.AskPrice = 100;
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;
Delegates Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi 04.11.2013 44/54 Asiminoaei Ioan Etapa 4: Definirea metodei responsabila cu notificarea clientilor despre aparitia evenimentului
// 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); Delegates Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi 04.11.2013 45/54 Asiminoaei Ioan } }
// 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).
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:
Delegates Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi 04.11.2013 46/54 Asiminoaei Ioan 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
private MailMsgEventHandler MailMsg = null;
// 2. A PUBLIC add_* method // Allows objects to register interest in the event.
// 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 04.11.2013 47/54 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.
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). In continuare obiectul Fax se inregistreaza pentru evenimentul MailMsg din mailManager :
Delegates Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi 04.11.2013 48/54 Asiminoaei Ioan mm.MailMsg += new MailManager.MailMsgEventHandler(FaxMsg);
Compilatorul C# va transforma operatorul += in urmatoarea linie de cod pentru a adauga interesul obiectului pentru eveniment :
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 04.11.2013 49/54 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
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 04.11.2013 50/54 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 04.11.2013 51/54 Asiminoaei Ioan Dup ace 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
// // ... cod ce poate fi executat dupa apelul BeginInvoke //
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 04.11.2013 52/54 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 04.11.2013 53/54 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(); CalledDelegate d;
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 04.11.2013 54/54 Asiminoaei Ioan // 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 . . .