Documente Academic
Documente Profesional
Documente Cultură
21.01.2007
Delegate
(Applied Microsoft .NET Framework Programming J. Richter)
Delegate suporta apelul metodelor statice si al metodelor instantei.
Metode callback folosite in tratarea notificarilor, exceptiilor, schimbarea starii ferestrelor,
selectie articole de meniu, operatii asincrone.
System;
System.Windows.Forms;
System.IO;
Set
private Object[] items;
public Set(Int32 numItems)
{
items = new Object[numItems];
Ioan Asiminoaei
Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
21.01.2007
class App
{
static void Main()
{
StaticCallbacks();
InstanceCallbacks();
}
static void StaticCallbacks()
{
// Creem un tablou cu 5 elemente de tip Set
Set setOfItems = new Set(5);
// Procesam articolele fara a furniza o metoda callback
setOfItems.ProcessItems(null);
Console.WriteLine();
//
// Procesam articolele si furnizam metoda
// callback FeedbackToConsole
//
setOfItems.ProcessItems(new Set.Feedback(App.FeedbackToConsole));
Console.WriteLine();
Ioan Asiminoaei
Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
21.01.2007
Ioan Asiminoaei
Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
21.01.2007
Descriere cod
Clasa Set constructorul creaza un tablou de obiecte, tablou cu dimensiune specificata.
Intializeaza fiecare element al tabloului cu un numar intreg.
Clasa Set defineste ca public delegate: Feedback.
Tot public se defineste si metoda ProcessItem ce are ca parametru o referinta la un
obiect delegate Feedback. Aceasta metoda scaneaza tabloul de elemente si pentru fiecare
element este apelata metoda callback.
Ioan Asiminoaei
Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
21.01.2007
Cand un nou obiect delegate Feedback este creat, se paseaza constructorului acestuia
appobj.FeedbackToFile. Efectul este ca delegate va construi o referinta la
metoda FeedbackToFile, care este o metoda a instantei (nu este o metoda statica). Cand
aceasta metoda a instantei este apelata, obiectul appobj se refera la obiectul pe care se va
lucra (pointer this).
Pentru metodele instantei, delegate are nevoie sa stie instanta obiectului ce contine
metoda.
Delegates in detaliu
Din punct de vedere al clientului: definim delegate, construim instanta si apelam metoda
(callback).
Din punctul de vedere al compilatorului si al CLR-ului lucrurile sunt mai complicate.
Vom examina codul:
public delegate void Feedback( Object value,
Int32 item,
Int32 numItems);
La intilnirea acestei linii de cod compilatorul va defini o clasa ce arata cam asa
(documentatie Microsoft):
public class Feedback : System.MulticastDelegate
{
// Constructor
public Feedback(Object target, Int32 methodPtr);
Ioan Asiminoaei
Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
21.01.2007
Vom discuta constructorul si metoda Invoke. Pentru a va convinge ca asa stau lucrurile
folositi ILDasm.exe.
Toate tipurile delegate sunt derivate din System.MulticastDelegate.
Clasa Feedback este derivata din System.MulticastDelegate, si numai compilatorul poate
face acest lucru cand intilneste cuvantul cheie delegate.
Modificatorul de acces al clasei este acelasi cu modificatorul de acces folosit la
declararea delegate.
Avem de a face cu o clasa definita in interiorul altei clase.
Campuri principale din System.MulticastDelegate
Camp
Tip
Descriere
_target
System.Object
Se refera la obiectul pe care se va lucra cand
metoda callback va fi apelata. Acest camp
este folosit pentru metode callback ale
instantei.
_methodPtr
System.Int32
Un intreg folosit intern de CLR pentru a
identifica metoda callback apelata.
_prev
System.MulticastDelegate Se refera la un alt obiect delegate. In mod
normal are valoarea null. Este folosit pentru
a crea o lista inlantuita de obiecte
MulticastDelegate.
Constructorii pentru delegate au doi parametri: o referinta la un obiect si un intreg ce
identifica metoda callback.
Secventa de cod ce transmite metoda callback nu foloseste forma ctor cu doi parametri :
Set setOfItems = new Set(5);
// Proceseaza articolele si apeleaza metoda FeedbackToFile.
Ioan Asiminoaei
Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
21.01.2007
In acest caz compilatorul adauga cod pentru identificarea metodei, mai precis pentru a
construi al doilea parametru al constructorului.
Pentru metodele instantei _target retine referinta la obiect, iar pentru metodele callback
statice, _target = null.
MulticastDelegate defineste doua proprietati: Target si Method.
Proprietatea Target returneaza o referinta la obiectul pe care se va lucra daca metoda
callback apelata este o metoda a instantei.
Daca metoda este statica Target returneaza null.
Proprietatea Method returneaza un obiect System.Reflection.MethodInfo,
obiect ce identifica metoda callback.
Putem folosi aceasta informatie in mai multe moduri. De exemplu, putem verifica daca
un obiect delegate se refera la metoda instanta a clasei de un anume tip :
Boolean DelegateRefersToInstanceMethodOfType(
MulticastDelegate d, Type type)
{
return((d.Target != null) && d.Target.GetType() == type);
}
Ioan Asiminoaei
Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
21.01.2007
Dupa cum se observa apelam o metoda cu numele feedback, desi nu am definit asa ceva.
Din declratie feedback este o variabila ce se refera la un obiect delegate.
Compilatorul va genera cod pentru a apela metoda Invoke a obiectului delegate.
Pentru linia de cod :
feedback(items[item], item, items.Length);
compilatorul va genera ceva de genul:
feedback.Invoke(items[item], item, items.Length);
Metoda Invoke nu poate fi apelata direct din codul sursa, compilator C#.
In Visual Basic trebuie apelata explicit metoda Invoke.
Metoda Invoke foloseste campurile _target si _methodPtr pentru a apela metoda corecta
pe obiectul specificat.
Metoda Invoke are aceeasi signatura ca si delegate.
Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
21.01.2007
}
Cand se construieste un obiect delegate, campul _prev = null. Pentru a construi lista
inlantuita de delegate folosim metodele Combine, ca mai jos :
Feedback fb1 = new Feedback(FeedbackToConsole);
Feedback fb2 = new Feedback(FeedbackToMsgBox);
Feedback fbChain = (Feedback) Delegate.Combine(fb1, fb2);
App appobj = new App();
Feedback fb3 = new Feedback(appobj.FeedbackToStream);
fbChain = (Feedback) Delegate.Combine(fbChain, fb3);
Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
21.01.2007
Daca delegate returneaza o valoare, si avem o lista de delegate, numai delegate din capul
listei va returna valoarea.
Ioan Asiminoaei
Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
21.01.2007
fb -= new Feedback(FeedbackToConsole);
Ioan Asiminoaei
Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
21.01.2007
class Speaker
{
// This method returns the speakers status.
public String Volume()
{
return "The volume is loud";
}
}
class App
{
// Definition of delegate that allows querying a components status
delegate String GetStatus();
static void Main()
{
// Declare an empty delegate chain.
GetStatus getStatus = null;
// Construct the three components, and add
// their status methods
// to the delegate chain.
getStatus += new GetStatus(new Light().SwitchPosition);
getStatus += new GetStatus(new Fan().Speed);
getStatus += new GetStatus(new Speaker().Volume);
// Show consolidated status report reflecting
// the condition of the three components.
Console.WriteLine(GetComponentStatusReport(getStatus));
}
// Method that queries several components
// and returns a status report
static String GetComponentStatusReport(GetStatus status)
{
// If the chain is empty, theres nothing to do.
if (status == null) return null;
// Use this to build the status report.
StringBuilder report = new StringBuilder();
// Get an array where each element
// is a delegate from the chain.
Delegate[] arrayOfDelegates = status.GetInvocationList();
// Iterate over each delegate in the array.
foreach (GetStatus getStatus in arrayOfDelegates)
{
try
{
// Get a components status string,
// and append it to the report.
Ioan Asiminoaei
Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
21.01.2007
report.AppendFormat("{0}{1}{1}",
getStatus(), Environment.NewLine);
}
catch (Exception e)
{
// Generate an error entry in the report
// for this component.
Object component = getStatus.Target;
report.AppendFormat(
"Failed to get status from
{1}{2}{0} Error: {3}{0}{0}",
Environment.NewLine,
((component == null) ? "" :
component.GetType() + "."),
getStatus.Method.Name, e.Message);
}
}
// Return the consolidated report to the caller.
return report.ToString();
}
}
Rezultatul este:
The light is off
Failed to get status from Fan.Speed
Error: The fan broke due to overheating
The volume is loud
Delegate si Reflection
In metodele callback discutate pina acum, informatia necesara (parametrii) erau stiuti in
momentul compilarii.
Tratam problema completarii acestor parametri in momentul executiei plus problema
apelarii unei anumite metode callback. Este exact ceea ce intimpla in programarea
Windows. Un eveniment genereaza un mesaj, daca exista un handler pentru mesaj acesta
va fi tratat, altfel nu.
Metodele din clasa delegate ce realizeaza acest lucru sunt:
public class Delegate
{
// Construct a delType delegate wrapping
// the specified methodInfo.
public static Delegate CreateDelegate(
Type delType,
MethodInfo mi);
Ioan Asiminoaei
Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
21.01.2007
Ioan Asiminoaei
Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
21.01.2007
{
static void Main(String[] args)
{
if (args.Length < 2)
{
String fileName = Path.GetFileNameWithoutExtension(
Assembly.GetEntryAssembly().CodeBase);
Console.WriteLine("Usage:");
Console.WriteLine("{0} delType methodName [Param1] [Param2]",
fileName);
Console.WriteLine(" where delType must be TwoInt32s or
OneString");
Console.WriteLine(" if delType is TwoInt32s, " +
"methodName must be Add or Subtract");
Console.WriteLine(" if delType is OneString, " +
"methodName must be NumChars or Reverse");
Console.WriteLine();
Console.WriteLine("Examples:");
Console.WriteLine(" {0} TwoInt32s Add 123 321", fileName);
Console.WriteLine(" {0} TwoInt32s Subtract 123 321", fileName);
Console.WriteLine(" {0} OneString NumChars \"Hello there\"",
fileName);
Console.WriteLine(" {0} OneString Reverse \"Hello there\"",
fileName);
return;
}
Type delType = Type.GetType(args[0]);
if (delType == null)
{
Console.WriteLine("Invalid delType argument: " + args[0]);
return;
}
Delegate d;
try
{
d = Delegate.CreateDelegate(delType, typeof(App), args[1]);
}
catch (ArgumentException)
{
Console.WriteLine("Invalid methodName argument: " +
args[1]);
return;
}
Object[] callbackArgs = new Object[args.Length - 2];
if (d.GetType() == typeof(TwoInt32s))
{
try
{
for (Int32 a = 2; a < args.Length; a++)
callbackArgs[a - 2] = Int32.Parse(args[a]);
}
catch (FormatException)
{
Ioan Asiminoaei
Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
21.01.2007
Ioan Asiminoaei
Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
21.01.2007
Ioan Asiminoaei
Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
21.01.2007
{
get { return change; }
}
};
// Publisher
class InventoryManager
{
public delegate void InventoryChangeEventHandler(
object source,
InventoryChangeEventArgs e);
OnInventoryChangeHandler(this, e);
}
}
};
// Subscriber
class InventoryWatcher
{
InventoryManager inventoryManager;
public InventoryWatcher(InventoryManager inventoryManager)
{
Console.WriteLine("[InventoryWatcher" +
".InventoryWatcher] Subscribing to " +
"InventoryChange event\n");
this.inventoryManager = inventoryManager;
inventoryManager.OnInventoryChangeHandler += new
InventoryManager.InventoryChangeEventHandler(OnInventoryChange);
}
// Metoda apelata la eveniment
void OnInventoryChange(object source,
InventoryChangeEventArgs e)
Ioan Asiminoaei
Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
21.01.2007
{
int change = e.Change;
Console.WriteLine("[InventoryManager.OnInventoryChange]" +
"\n\tPart '{0}' was {1} by {2} units\n",
e.Sku,
change > 0 ? "increased" : "decreased",
Math.Abs(e.Change));
}
}
// test tipuri definite
class DelegateEvents
{
public static void Main()
{
InventoryManager inventoryManager =
new InventoryManager();
Console.WriteLine("[DelegateEvents.Main] " +
"Instantiating subscriber object\n");
InventoryWatcher inventoryWatch =
new InventoryWatcher(inventoryManager);
inventoryManager.UpdateInventory("111 006 116", -2);
inventoryManager.UpdateInventory("111 005 383", 5);
Console.ReadLine();
}
};
Aici apare cuvatul cheie event, ce defineste un membru de tip event, cu care specificam
delegate si metoda(ele) ve va (vor) fi apelata (e) cand evenimentul este generat.
In exemplul de mai sus UpdateInventory va fi apelata ori de cite ori se genereaza
evenimentul ce anunta modificarea stocului.
Aceasta metoda creaza un obiect de tipul InventoryChangeEventArgs. Acest obiect este
pasat tuturor celor ce au subscris pentru acest eveniment, si este utilizat sa descrie
evenimentul.
In urmatorul cod:
if (OnInventoryChangeHandler != null)
Ioan Asiminoaei
Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
21.01.2007
{
Console.WriteLine("[InventoryManager" +
".UpdateInventory] Raising event to " +
"all subscribers...\n");
OnInventoryChangeHandler(this, e);
}
instructiunea if verifica daca pentru acest eveniment exista obiecte ce asteapta producerea
lui.
Sa examinam codul din cei ce s-au inscris pentru eveniment. In situatia de fata clasa
InventoryWatcher.
Ceea ce are nevoie sa faca aceasta clasa este:
sa instantieze un delegate de tip InventoryManager.InventoryChangeEventHandler si
sa adauge acest delegate la evenimentul
InventoryManager.OnInventoryChangeHandler.
inventoryManager.OnInventoryChangeHandler += new
InventoryManager.InventoryChangeEventHandler(
OnInventoryChange);
Ioan Asiminoaei
Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
21.01.2007
Evenimente
Membrul event al unei clase permite acesteia de a notifica alte obiecte ca ceva s-a
intimplat.
Un tip ce defineste un membru event are urmatoarele capabilitati:
Ioan Asiminoaei
Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
21.01.2007
Ioan Asiminoaei
Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
21.01.2007
OnMailMsg(e);
}
}
Acesta serveste ca un tip de baza din care alte tipuri pot deriva.
Exista si evenimente care nu au informatii aditionale de transmis (clic pe un buton).
Cand definim un eveniment ce nu are informatii aditionale de transmis putem folosi
EventArgs.Empty in loc de a construi un nou obiect EventArgs.
2. Definim un tip delegate, specificand prototipul metodei ce va fi apelata cand se
genereaza evenimentul. Prin conventie numele delegate se termina cu EventHandler.
Prototipul trebuie sa returneze void si sa aiba doi parametri. Primul parametru este un
Object ce se refera la la obiectul ce a trimis notificarea, iar al doilea parametru este un tip
derivat din EventArgs ce contine informatii aditionale despre notificare.
Prototipul pentru EventHandler este urmatorul:
Ioan Asiminoaei
Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
21.01.2007
Cand un obiect este interesat de eveniment, acest camp referentiaza o instanta a delegate
MailMsgEventHandler.
Fiecare instanta delegate MailMsgEventHandler are un pointer la un alt delegate
MailMsgEventHandler sau la null pentru a marca sfarsitul listei.
Ioan Asiminoaei
Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
21.01.2007
Ioan Asiminoaei
Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
21.01.2007
Ioan Asiminoaei
Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
21.01.2007
// Class with a function that creates the eventargs and initiates the
event
public class FireAlarm {
// Events are handled with delegates, so we must establish a
FireEventHandler
// as a delegate:
public delegate void FireEventHandler(object sender, FireEventArgs
fe);
// Now, create a public event "FireEvent" whose type is our
FireEventHandler delegate.
public event FireEventHandler FireEvent;
// This will be the starting point of our event-- it will create
FireEventArgs,
// and then raise the event, passing FireEventArgs.
public void ActivateFireAlarm(string room, int ferocity) {
FireEventArgs fireArgs = new FireEventArgs(room, ferocity);
// Now, raise the event by invoking the delegate. Pass in
// the object that initated the event (this) as well as
FireEventArgs.
Ioan Asiminoaei
Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
21.01.2007
Ioan Asiminoaei
Delegates
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
21.01.2007
//end of main
// end of FireEventTest
Ioan Asiminoaei