Méthodes Anonymes Delphi - John COLIBRI. |
- résumé : présentation, capture de variables et clôture, Invoke, implémentation et analyse mémoire, exemple profiling, énumérateur filtré, traitement de pages téléchargées.
- mots clé : reference to, méthodes anonymes, Invoke, iEnumerator, iEnumerable, tThread, Synchronize
- logiciel utilisé : Windows XP personnel, Delphi Xe3
- matériel utilisé : Pentium 2.800 Mhz, 512 Meg de mémoire, 250 Giga disque dur
- champ d'application : Delphi 2009, Delphi 2010, Delphi Xe, Delphi Xe2, Delphi Xe3
- niveau : développeur Delphi
- plan :
1 - Delphi Anonymous Methods
Les méthodes anonymes, introduites avec Delphi 2009 permettent essentiellement de fournir en paramètre à une procédure les instructions que cette procédure doit utiliser, un peu comme une procédure locale.
Les traitements effectués par les méthodes anonymes peut fort bien être codé par les techniques traditionnelles. Néanmoins - elles permettent un style de programmation qui s'approche un peu plus du style fonctionnel
- elles facilitent certains codes, allant du plus connu, tThread.Synchronize, aux exemples plus complexes comme les énumérateurs ou les delegates
2 - Premier exemple de Méthode Anonyme Delphi 2.1 - Appel de procédure normal Voici un exemple simple ou une clic appelle une procédure qui additionne deux nombre et affiche le résultat :
Procedure do_add(p_one, p_two: Integer; Var pv_result: Integer);
Begin pv_result:= p_one+ p_two;
End; // do_add
Procedure TForm1.procedure_call_Click(Sender: TObject);
Var l_one, l_two, l_result: Integer;
Begin l_one:= 11; l_two:= 22;
do_add(l_one, l_two, l_result);
display(Format('%d + %d = %d ', [l_one, l_two, l_result]));
End; // procedure_call_Click |
Une version plus encapsulée pourrait utiliser une procédure locale:
Procedure TForm1.local_procedure_Click(Sender: TObject);
Var l_one, l_two, l_result: Integer;
Procedure do_add; Begin
l_result:= l_one+ l_two;
End; // do_add Begin // local_procedure_Click
l_one:= 11; l_two:= 22; do_add;
display(Format('%d + %d = %d ', [l_one, l_two, l_result]));
End; // local_procedure_Click |
2.2 - Utilisation d'une méthode anonyme
Pour utiliser les méthodes anonymes, nous DEVONS d'abord définir la signature de la méthode (Procedure ou Function) en utilisant Type:
Type t_ap_add= Reference To Procedure(p_one, p_two: Integer; Var pv_result: Integer);
|
Nous pouvons alors - définir une variable du type procédure anonyme
- initialiser cette variable en fournissant les instructions qui composent cette méthode
- appeler cette méthode
Procedure TForm1.using_anon_Click(Sender: TObject);
Var l_ap_add: t_ap_add;
l_one, l_two, l_result: Integer;
Begin
l_ap_add:= Procedure(p_one, p_two: Integer; Var pv_result: Integer)
Begin
pv_result:= p_one+ p_two;
End; l_one:= 11; l_two:= 22;
l_ap_add(l_one, l_two, l_result);
display(Format('%d + %d = %d ', [l_one, l_two, l_result]));
End; // using_anon_Click |
A ce stade, nous avons remplacé deux procédures par un type et une seule procédure.
Une des difficultés est de trouver des exemples simples qui donneraient envie d'utiliser ces procédure anonymes. A la fin de cet article, nous tenterons d'en présenter quelques uns.
En attendant, voici quelques variations sur le même thème de l'opération binaire: - nous définissons une Function anonyme, et effectuons des opérations différentes avec ce type:
Type t_af_binary_operation= Reference To Function(p_one, p_two: Integer): Integer;
Procedure TForm1.several_operations_Click(Sender: TObject);
Var l_one, l_two: Integer;
l_af_binary_operation: t_af_binary_operation;
l_result: Integer; Begin
l_one:= 11; l_two:= 22;
l_af_binary_operation:= Function(p_one, p_two: Integer): Integer
Begin
Result:= p_one+ p_two; End;
l_result:= l_af_binary_operation(l_one, l_two);
display(Format('%d + %d = %d ', [l_one, l_two, l_result]));
l_af_binary_operation:= Function(p_one, p_two: Integer): Integer
Begin
Result:= p_one* p_two; End;
display(Format('%d * %d = %d ',
[l_one, l_two, l_af_binary_operation(l_one, l_two)]));
End; // several_operations_Click | - ou encore, nous avons une procédure qui se charge d'afficher le résultat
d'une opération binaire, et nous appelons cette procédure en fournissant différents types d'opérations:
Type t_af_binary_operation= Reference To Function(p_one, p_two: Integer): Integer;
Procedure display_binary_operation(p_one, p_two: Integer;
p_operator: Char; p_af_binary_operation: t_af_binary_operation);
Begin display(Format('%d %s %d = %d ',
[p_one, p_operator, p_two, p_af_binary_operation(p_one, p_two)]));
End; // display_binary_operation
Procedure TForm1.several_calls_Click(Sender: TObject);
Begin display_binary_operation(11, 22, '+',
Function(p_one, p_two: Integer): Integer
Begin
Result:= p_one+ p_two;
End); display_binary_operation(11, 22, '*',
Function(p_one, p_two: Integer): Integer
Begin
Result:= p_one* p_two;
End) End; // several_calls_Click |
2.3 - Types de sous-programmes Delphi 2.3.1 - Type Procédural Le dernier exemple est en fait dans la ligne droite des types procéduraux
inclus par Niklaus WIRTH dans la spécification Pascal, mais qui n'était implémentée ni dans le compilateur P4, ni dans le Pascal de l'Apple ][. Turbo Pascal était l'un des premiers compilateurs à offrir une implémentation.
L'idée de WIRTH était de pouvoir créer une procédure effectuant un traitement complexe, comme une intégration symbolique, en laissait l'utilisateur définir la fonction à intégrer (et sans doute les bornes, dans cet exemple):
Type t_ft_function= Function(p_x: Double): Double:
Function f_integrate(p_ft_function: t_ft_function; p_a, p_b: Double): Double;
Begin // -- here, complex code for symbolic integration of the function
// -- from p_a to p_b End; // f_integrate
Function f_my_function(p_x: Double): Double;
Begin // -- here computes f(xà
End; // f_my_function
Procedure TForm1.button_1_Click(Sender: TObject);
Var l_result: Double; Begin
l_result:= f_integrate(Sin, 1, 2); // ...
l_result:= f_integrate(Gamma, 10, 20) // ...
l_result:= f_integrate(f_x, 1.5, 3.14);
End; // button_1_Click |
Dans cet exemple
- nous supposons nos fonctions définies par la RTL, ou nous aurions pu créer une fonction nous-mêmes
- la routine qui se charge de l'intégration reçoit comme paramètre la fonction à intégrer
- le programme appelant l'intégration fournit la fonction à utiliser comme paramètre
2.3.2 - Procedure Of Object - Evenements En programmation objet, chaque méthode a un paramètre muet Self qui est
poussé sur la pile avant l'appel de cette méthode. Il a donc fallu créer un type procédural spécial, ce qui correspond à des Procedure Of Object ou Function of Object. Voici un exemple simple:
- nous définissons une Classe et un type Function Of Object:
Type c_person= Class
m_first_name: String;
m_age: integer;
Constructor create_person(
p_first_name: String; p_age: Integer);
End; // c_person
t_po_display_person= Procedure(p_c_person: c_person) Of Object;
| - nous DEVONS mettre la véritable fonction dans "un" objet. Par exemple la Forme
Type TForm1 = Class(TForm)
// ooo Private
Function display_person(p_c_person: c_person): String;
End;
Procedure TForm1.display_person(p_c_person: c_person);
Begin display(Format('person=%s, Self.Classname= %s',
[p_c_person.m_first_name, Self.ClassName]));
End; // display_person | - et nous pouvons déclarer des variables de type Procedure of Object, leur
affecter l'adresse de n'importe quelle procédure d'un objet ayant la même signature (ici une c_person) et l'appeler en fournissant un objet c_person:
Procedure TForm1.procedure_of_object_Click(Sender: TObject);
Var l_c_person: c_person;
l_po_display_person: t_po_display_person; Begin
l_c_person:= c_person.create_person('joe', 33);
l_po_display_person:= display_person; l_po_display_person(l_c_person);
l_c_person.Free; End; // procedure_of_object_Click |
Notez aussi que - tous les événements Delphi sont des Procedure Of Object. Le plus connu est tNotifyEvent, utilisé pour OnClick, entre autres:
// System.Classes
TNotifyEvent = Procedure(Sender: TObject) Of Object;
// Vcl.Controls TControl= Class(tComponent)
FOnClick: TNotifyEvent; // ooo
Property OnClick: TNotifyEvent read FOnClick write FOnClick // ooo
End; // tControl |
2.3.3 - Les Méthodes Anonymes
Les méthodes anonymes sont le troisième type procédural, et se distinguent des deux précédentes par les mots clé Reference To:
Type t_ap_add= Reference To Procedure(p_one, p_two: Integer; Var pv_result: Integer);
|
3 - Capture de variable - Clôture 3.1 - Anonymous Method Closure - Exemple Les méthodes anonymes ont aussi la propriété de transporter avec elles les
données locales qu'elles "capturent" lors de leur définition. Ce concept va à l'encontre de tout ce que nous connaissions des variables locales. Toutefois, ce n'est pas l'élément principal des anonymes, et si vous
êtes mal à l'aise avec cette mécanique, n'utilisez pas la cloture, et vous pourrez tout de même tirer les trois quart des bénéfices des anonymes.
Voici tout d'abord un exemple simple, sans clôture, où nous créons sur un clic
une variable pointant vers une méthode anonyme, et nous utilisons cette variable sur un autre clic:
Type t_ap_add= Reference To Procedure(p_one, p_two: Integer; Var pv_result: Integer);
|
A présent, voici un exemple où la définition de la méthode anonyme utilise une variable locale: - nous définissons la méthode anonyme suivante :
Type t_ap_integer = Reference To Procedure(p_integer: Integer);
| - nous ajoutons une procédure qui appelle cette méthode anonyme:
Procedure call_anon(p_ap_integer: t_ap_integer; p_integer: Integer);
Begin p_ap_integer(p_integer);
End; // call_anon | - dans un clic nou appelons cette méthode en définissant la méthode anonyme:
Procedure TForm1.capture_of_local_Click(Sender: TObject);
Var l_total: Integer; Begin
l_total:= 100;
display(Format('initial_l_total %4d', [l_total]));
call_anon( Procedure(p_increment: Integer)
Begin
// -- will CHANGE the local
Inc(l_total, p_increment);
display(Format('captured_l_total %4d anon_param_p_increment %4d',
[l_total, p_increment]));
End, 33);
display('local_after_call '+ IntToStr(l_total));
End; // capture_of_local_Click | et voici un exemple d'exécution après 2 clics:
Rien de surprenant, chaque clic: - réinitialise la locale à 100
- appelle la procédure anonyme qui modifie cette variable (133)
Chaque chaque clic fournit donc le même résultat.
Voici à présent la capture de la locale, avec cumul à chaque appel
Les appels successifs ont pu modifier la variable locale l_total, car au moment de la définition de la méthode anonyme, la globale g_ap_xxx a stocké - les instructions à exécuter (l'incrémentation et l'affichage)
- les variables locales inclues dans la définition (ici l_total)
Les méthodes anonymes peuvent ainsi être décrites comme
les méthodes anonymes permettent de créer le code d'une méthode dans un contexte |
4 - Implémentation des méthodes anonymes
4.1 - Objet Sous-Jacent Pour implémenter les procédures anonymes, - le compilateur définit une Interface spéciale pour chaque définition de référence de méthode. Cette Interface a une seule méthode, qui est notre
méthode anonyme
- lors de la création ou l'invocation, notre projet créé un objet
- qui implémente cette Interface
- qui capture l'environnement au moment de l'invocation
- ces Interfaces utilisent le comptage de référence COM, et l'objet est donc libéré automatiquement
Au niveau durée de vie, l'objet COM subsiste tant que quelqu'un référence cet objet. Si l'anonyme a capturé une variable, cet anonyme étend la durée de vie de cette variable, tant que quelqu'un utilise cet anonyme.
Notons au passage que "méthodes" anonyme vient du fait que les anonymes sont en fait les méthodes d'une Classe implicite.
4.2 - Capture de l'adresse Comme le compilateur copie sur le tas les variables capturées, si nous
modifions la valeur d'une locale capturée, c'est la dernière valeur qui est stockée dans la capture. Voici la définition d'une anonyme globale qui capture une local:
Type t_ap_integer= Reference To Procedure(p_integer: Integer);
Var g_ap_integer: t_ap_integer;
Procedure TForm1.define_g_ap_and_capture_local_Click(Sender: TObject);
Var l_integer: Integer; Begin
l_integer:= 33;
display(Format('initial_l_integer %4d', [l_integer]));
// -- capture the local g_ap_integer:=
Procedure(p_increment: Integer)
Begin
Inc(l_integer, p_increment);
display(Format('captured_l_integer %4d anon_param_p_increment %4d',
[l_integer, p_increment]));
End; // -- change the local "after" the capture
l_integer:= 77; End; // define_g_ap_and_capture_local_Click |
Puis nous appelons la méthode anonyme:
Procedure TForm1.call_g_apClick(Sender: TObject);
Begin g_ap_integer(1000); End; // call_g_apClick |
et voici le résultat :
Comme l'objet anonyme a été crée, il stocke dans son champ les valeurs
successives de la variable capturée. Donc, même si, au moment de la capture, la locale a la valeur 33, l'affectation (au champ de l'objet anonyme) de 77 conserve cette valeur, qui est ensuite affichée.
Formulé autrement, les anonymes capturent l'adresse des variables de l'environnement, et ne capturent PAS les VALEURS des variables capturées
4.3 - Dump Anonymous
Pour vérifier toute ces assertions, nous pouvons afficher ces fameux objets et interfaces qui sont utilisés pour implémenter les anonymes. Nous nous appuyons ici sur les articles de Barry KELLY et Cosmin PRUND.
Nous avons placé dans une unité les méthodes de dump:
Function f_invoke_address(Const pk_ap): String;
Type t_vmt_array= Array[0..3] Of Pointer;
t_pt_vmt_array= ^t_vmt_array;
t_pt_pt_vmt_array= ^t_pt_vmt_array; Begin
// -- 3 is offset of Invoke, after QI, AddRef, Release
Result:= Format('invoke=$%6x',
[Integer(t_pt_pt_vmt_array(pk_ap)^^[3])]);
End; // f_invoke_address
Function f_anonymous_to_object(p_pt_anonymous: Pointer): tObject;
Var l_i_interface: iInterface; Begin
l_i_interface := PUnknown(p_pt_anonymous)^;
Result:= l_i_interface As TObject;
End; // f_anonymous_to_object
Procedure display_anonypous(p_title: String; p_pt_anonymous: Pointer);
Var l_i_interface: iInterface;
l_c_object: tObject;
l_c_rtti_context: TRttiContext;
l_c_rtti_type: TRttiType;
l_c_rtti_field: TRttiField; Begin
l_i_interface := PUnknown(p_pt_anonymous)^;
l_c_object:= l_i_interface As TObject;
display(Format('%s [@=(sSelf)=%4x i=$%4x o=$%4x %s]',
[p_title, Integer(p_pt_anonymous), Integer(@l_i_interface), Integer(@l_c_object),
f_invoke_address(p_pt_anonymous)]) );
l_c_object:= f_anonymous_to_object(p_pt_anonymous);
display(' ClassName '+ l_c_object.ClassName);
display(' Parent.ClassName '+ l_c_object.ClassType.ClassParent.ClassName);
l_c_rtti_type := l_c_rtti_context.GetType(l_c_object.ClassType);
For l_c_rtti_field In l_c_rtti_type.GetFields Do
display(' fields '+ l_c_rtti_field.Name + ':' + l_c_rtti_field.FieldType.Name);
End; // display_anonypous |
Et voici quelques exemples:
Function f_invoke_address(Const pk_ap): String;
Type t_vmt_array= Array[0..3] Of Pointer;
t_pt_vmt_array= ^t_vmt_array;
t_pt_pt_vmt_array= ^t_pt_vmt_array; Begin
// -- 3 is offset of Invoke, after QI, AddRef, Release
Result:= Format('invoke=$%6x',
[Integer(t_pt_pt_vmt_array(pk_ap)^^[3])]);
End; // f_invoke_address
Function f_anonymous_to_object(p_pt_anonymous: Pointer): tObject;
Var l_i_interface: iInterface; Begin
l_i_interface := PUnknown(p_pt_anonymous)^;
Result:= l_i_interface As TObject;
End; // f_anonymous_to_object
Procedure display_anonypous(p_title: String; p_pt_anonymous: Pointer);
Var l_i_interface: iInterface;
l_c_object: tObject;
l_c_rtti_context: TRttiContext;
l_c_rtti_type: TRttiType;
l_c_rtti_field: TRttiField; Begin
l_i_interface := PUnknown(p_pt_anonymous)^;
l_c_object:= l_i_interface As TObject;
display(Format('%s [@=(sSelf)=%4x i=$%4x o=$%4x %s]',
[p_title, Integer(p_pt_anonymous), Integer(@l_i_interface), Integer(@l_c_object),
f_invoke_address(p_pt_anonymous)]) );
l_c_object:= f_anonymous_to_object(p_pt_anonymous);
display(' ClassName '+ l_c_object.ClassName);
display(' Parent.ClassName '+ l_c_object.ClassType.ClassParent.ClassName);
l_c_rtti_type := l_c_rtti_context.GetType(l_c_object.ClassType);
For l_c_rtti_field In l_c_rtti_type.GetFields Do
display(' fields '+ l_c_rtti_field.Name + ':' + l_c_rtti_field.FieldType.Name);
End; // display_anonypous | et voici le résultat: Nous pouvons bien afficher les objets et les interfaces liés aux anonymes. L'interprétation des objets réellement créés reste à faire.
4.4 - Limitations
Au niveau utilisation, les anonymes sont aussi liés aux procédure Inline. Ils sont donc de ce fait limités ainsi: - une méthode anonyme ne peut PAS capturer l'indice d'une boucle For
- une méthode anonyme ne peut PAS capturer le Result d'une fonction englobante (le Result d'une Function anonyme, mais pas le Result d'une Function qui est en train de définir une anonyme
- nous avons aussi rencontré des interdiction au niveau de l'emboîtement de procédures utilisant des anonymes (cf ci-dessous le filtrage d'une tList<T>)
4.5 - () et Invoke
Pour Niklaus WIRTH, si une procédure ou une fonction n'a pas de paramètres, il n'est pas permis - de la définir avec des parenthèses vides ()
- de l'appeler avec des parenthèses vides ()
Delphi, par analogie où toutes les routines, paramètres ou non, sont définies et invoquées avec des parenthèses, Delphi permet d'utiliser des parenthèses vides:
Procedure compute; Begin
End; // compute Procedure process();
Begin End; // process
Procedure TForm1.procedure_Click(Sender: TObject);
Begin compute; compute();
process; process(); End; // procedure_Click |
Lorsque nous utilisons une Function qui génère une méthode anonyme (Result est une méthode anonyme), il peut y avoir des ambiguités entre les parenthèses
de l'appel de la fonction de génération et les parenthèses des paramètres de la procédure anonyme. C'est un problème un peu similaire au déréférencement des pointeurs. Jadis, en
PASCAL, nous avions l_pt_integer et l_pt_integer^ pour distinguer le pointeur et la valeur pointée. Comme Delphi utilise un modèle par référence, il a fallu
utiliser le mot Assigned pour tester si un événement est NIL, car le test mon_événementNIL est impossible. Pour soulager les problèmes, Delphi permet l'utilisation de Invoke pour
provoquer l'appel d'une méthode anonyme et le distinguer de l'appel d'une fonction qui génère une anonyme.
Voici quelques exemples qui illustrent ces problèmes - nos anonymes sont définies par:
Type t_ap= Reference To Procedure;
t_ap_integer= Reference To Procedure(p_n: Integer);
| - tout d'abord générateur sans paramètre, anonyme sans paramètre
Function f_ap: t_ap;
Var l_integer: Integer; Begin
l_integer:= 100; Result:= Procedure
Begin
l_integer:= l_integer;
display(IntToStr (l_integer));
End; End; // f_ap
Procedure TForm1.ap_Click(Sender: TObject);
Var l_ap: t_ap; Begin
display(''); display('l_ap NO'); (*
l_ap:= f_ap; l_ap; *) display('l_ap');
l_ap:= f_ap(); l_ap;
display('f_ap()'); f_ap();
display('f_ap()()'); f_ap()();
display('f_ap().Invoke'); f_ap().Invoke;
display('f_ap.Invoke'); f_ap.Invoke;
End; // ap_Click | - générateur avec paramètre, anonyme sans paramètre
Function f_n_ap(p_total: Integer): t_ap;
Begin Result:= Procedure
Begin
p_total:= p_total+ 3;
display(IntToStr(p_total));
End; End; // f_n_ap
Procedure TForm1.n_ap_Click(Sender: TObject);
Var l_ap: t_ap; Begin
display(''); display('l_ap NO'); (*
l_ap:= f_n_ap; l_ap; *) display('l_ap(20)');
l_ap:= f_n_ap(20); l_ap;
display('f_n_ap(20)'); f_n_ap(20);
display('f_n_ap(20)()'); f_n_ap(20)();
display('f_n_ap(20).Invoke'); f_n_ap(20).Invoke;
display('f_n_ap.Invoke NO'); (* f_n_ap.Invoke; *)
End; // n_ap_Click | - générateur sans paramètre, anonyme avec paramètre
Function f_ap_integer: t_ap_integer;
Var l_integer: Integer; Begin
l_integer:= 100; Result:=
Procedure (p_n: Integer)
Begin
l_integer:= l_integer + p_n;
display(IntToStr (l_integer));
End; End; // f_ap_integer
Procedure TForm1.ap_integer_Click(Sender: TObject);
Var l_ap_integer: t_ap_integer; Begin
display('l_ap_integer(3)'); l_ap_integer:= f_ap_integer();
l_ap_integer(3); display('f_ap_integer()');
f_ap_integer(); display('f_ap_integer()(3)');
f_ap_integer()(3); display('f_ap_integer().Invoke(3)');
f_ap_integer().Invoke(3); display('f_ap_integer.Invoke(3)');
f_ap_integer.Invoke(3); End; // ap_integer_Click |
- finalement générateur avec paramètre, anonyme avec paramètre
Function f_n_ap_integer(p_total: Integer): t_ap_integer;
Begin Result:=
Procedure (p_n: Integer)
Begin
p_total:= p_total + p_n;
display(IntToStr(p_total));
End; End; // f_ap_integer
Procedure TForm1.n_ap_integer_Click(Sender: TObject);
Var l_ap_integer: t_ap_integer; Begin
display('l_ap_integer(3)'); l_ap_integer:= f_n_ap_integer(20);
l_ap_integer(3); display('f_n_ap_integer(20)');
f_n_ap_integer(20); display('f_n_ap_integer(20)(3)');
f_n_ap_integer(20)(3); display('f_n_ap_integer(20).Invoke(3)');
f_n_ap_integer(20).Invoke(3); display('f_n_ap_integer.Invoke(3) NO');
(* f_n_ap_integer.Invoke(3); *) End; // n_ap_integer_Click |
4.6 - Erreurs de syntaxe Mentionnons aussi qu'il faut être vigilant à utiliser la même signature pour la définition du type anonyme et la définition de cet anonyme. Voici un exemple avec un paramètre Const :
Type t_ap_integer= Reference To Procedure(Const pk_n: Integer);
Procedure TForm1.const_param_Click(Sender: TObject);
Var l_ap_integer: t_ap_integer; Begin
l_ap_integer:=
Procedure(Const pk_n: Integer)
Begin
display(IntToStr(pk_n));
End; l_ap_integer(33);
(* "incopatible type" l_ap_integer:= Procedure(pk_n: Integer)
begin display(IntToStr(pk_n)); end; *)
End; // const_param_Click |
4.7 - Méthodes Anonymes pré-définies
Delphi prédéfinit déjà dans SYSUTILS.PAS certains types références courants
Type TProc = Reference To Procedure;
TProc<T> = Reference To Procedure (Arg1: T);
TProc<T1,T2> = Reference To Procedure (Arg1: T1; Arg2: T2);
TProc<T1,T2,T3> = Reference To Procedure (Arg1: T1; Arg2: T2; Arg3: T3);
TProc<T1,T2,T3,T4> = Reference To Procedure (Arg1: T1; Arg2: T2; Arg3: T3; Arg4: T4);
TFunc<TResult> = Reference To Function: TResult;
TFunc<T,TResult> = Reference To Function (Arg1: T): TResult;
TFunc<T1,T2,TResult> = Reference To Function (Arg1: T1; Arg2: T2): TResult;
TFunc<T1,T2,T3,TResult> = Reference To Function (Arg1: T1; Arg2: T2; Arg3: T3): TResult;
TFunc<T1,T2,T3,T4,TResult> = Reference To Function (Arg1: T1; Arg2: T2; Arg3: T3; Arg4: T4): TResult;
TPredicate<T> = Reference To Function (Arg1: T): Boolean;
| Soulignons que pour les fonctions paramétrées, Result est, par convention, le dernier paramètre
5 - Exemples d'utilisation de méthodes anonymes
5.1 - Profiling Notre premier exemple concerne le profiling: nous souhaitons chronométrer différentes façons de coder un traitement. Habituellement, pour mesurer le temps d'exécution d'une procédure,
- nous sauvegardons l'heure (les ticks) initiale
- nous exécutons la procédure plusieurs fois
- nous mesurons l'heure finale, et calculons le temps moyen par exécution
Pour plus de précision, il faut effectuer plusieurs itérations, en éliminant autant que possible les temps de création, allocation etc.
Si la mise en oeuvre des mesure est plus lourde, il peut être intéressant d'inverser la mécanique: - nous construisons une classe qui se charge des paramètres et calibrages
- nous fournissons à cette classe la procédure à mesurer, sous forme de méthode anonyme
Voici donc - notre Classe de profiling:
Type c_procedure_timer=
Class
m_initial_iterations: Integer;
m_iterations: Integer;
m_base_frequency: Int64;
Constructor Create;
Procedure read_hires_frequency;
Function f_measure_time_of(
p_pa_to_profile: TProc;
p_initial_iterations, p_iterations: Integer): Double;
Procedure measure_time(
Const p_title: string;
Const p_pa_to_profile: TProc); Overload;
End; // c_procedure_timer
Constructor c_procedure_timer.Create; Begin
read_hires_frequency; m_initial_iterations:= k_default_overhead;
m_iterations:= k_default_iterations;
// -- try to eliminate initial setup time f_measure_time_of(
Procedure Begin
End, 100, 3);
End; // Create
Procedure c_procedure_timer.read_hires_frequency; Begin
QueryPerformanceFrequency(m_base_frequency) End; // read_hires_frequency
Function c_procedure_timer.f_measure_time_of(
p_pa_to_profile: TProc;
p_initial_iterations, p_iterations: Integer): Double;
Var l_start_ticks, l_stop_ticks: Int64;
l_iteration: Integer; Begin
// -- try to eliminate initial setup time
For l_iteration:= 1 To p_initial_iterations Do
p_pa_to_profile; QueryPerformanceCounter(l_start_ticks);
For l_iteration:= 1 To p_iterations Do
p_pa_to_profile; QueryPerformanceCounter(l_stop_ticks);
Result:= (l_stop_ticks- l_start_ticks)/ m_base_frequency/ p_iterations* 1000;
End; // f_measure_time_of | - nous souhaitons tester la performance de divers types d'appels : virtuels,
récursif, utilisant des objets ou des pointeurs d'Interface :
Type i_my_interface= Interface
Procedure const_interface_call(Const p_i_my_interface: i_my_interface; p_count: Integer);
Procedure interface_call(p_i_my_interface: i_my_interface; p_count: Integer);
End; // i_my_interface c_my_class=
Class(TInterfacedObject, i_my_interface)
Procedure const_non_virtual_call(Const p_c_my_class: c_my_class; p_count: Integer);
Procedure non_virtual_call(p_c_my_class: c_my_class; p_count: Integer);
Procedure virtual_call(p_c_my_class: c_my_class; p_count: Integer); Virtual;
Procedure const_interface_call(Const p_i_my_interface: i_my_interface; p_count: Integer);
Procedure interface_call(p_i_my_interface: i_my_interface; p_count: Integer);
End; // c_my_class
Procedure recursive_call(p_count: Integer);
Type t_ap= Reference To Procedure(p_ap: t_ap; p_count: Integer);
Type t_ap_const= Reference To Procedure(Const pk_ap: t_ap_const; p_count: Integer);
Procedure recursive_call(p_count: Integer); Begin
If p_count> 0
Then recursive_call(p_count- 1);
End; // recursive_call
Procedure c_my_class.interface_call(p_i_my_interface: i_my_interface; p_count: Integer);
Begin If p_count> 0
Then p_i_my_interface.interface_call(p_i_my_interface, p_count- 1);
End; // interface_call
Procedure c_my_class.const_interface_call(Const p_i_my_interface: i_my_interface; p_count: Integer);
Begin If p_count> 0
Then p_i_my_interface.const_interface_call(p_i_my_interface, p_count- 1);
End; // const_interface_call
Procedure c_my_class.non_virtual_call(p_c_my_class: c_my_class; p_count: Integer);
Begin If p_count> 0
Then non_virtual_call(p_c_my_class, p_count- 1);
End; // non_virtual_call
Procedure c_my_class.const_non_virtual_call(Const p_c_my_class: c_my_class; p_count: Integer);
Begin If p_count> 0
Then const_non_virtual_call(p_c_my_class, p_count- 1);
End; // const_non_virtual_call
Procedure c_my_class.virtual_call(p_c_my_class: c_my_class; p_count: Integer);
Begin If p_count> 0
Then virtual_call(p_c_my_class, p_count- 1);
End; // virtual_call | - voici l'utilisation de notre timer:
Procedure TForm1.compare_calls_Click(Sender: TObject);
Const k_recursive_count= 10000;
Var l_c_procedure_timer: c_procedure_timer;
l_c_my_object: c_my_class;
l_i_my_interface: i_my_interface; Begin
display('');
l_c_procedure_timer:= c_procedure_timer.Create; Try
l_c_procedure_timer.m_initial_iterations:= 100;
l_c_procedure_timer.m_iterations:= 100;
l_c_my_object:= c_my_class.Create;
l_i_my_interface:= l_c_my_object; display('');
l_c_procedure_timer.measure_time('procedure(n)',
Procedure Begin
recursive_call(k_recursive_count);
End );
display('');
l_c_procedure_timer.measure_time('l_c.method(p_c, n)',
Procedure Begin
l_c_my_object.non_virtual_call(l_c_my_object, k_recursive_count);
End );
l_c_procedure_timer.measure_time('l_c.method(const p_c, n)',
Procedure Begin
l_c_my_object.const_non_virtual_call(l_c_my_object, k_recursive_count);
End );
display('');
l_c_procedure_timer.measure_time('l_c.method(p_c, n) Virt',
Procedure Begin
l_c_my_object.virtual_call(l_c_my_object, k_recursive_count);
End );
display('');
l_c_procedure_timer.measure_time('l_i.method(p_i, n)',
Procedure Begin
l_i_my_interface.interface_call(l_i_my_interface, k_recursive_count);
End );
l_c_procedure_timer.measure_time('l_i.method(const p_i, n)',
Procedure Begin
l_i_my_interface.const_interface_call(l_i_my_interface, k_recursive_count);
End );
Finally l_c_procedure_timer.Free;
End; End; // compare_calls_Click |
- et voici le résultat de l'exécution :
Nous avons aussi profilé les méthodes anonymes: - voici le test
Procedure TForm1.measure_anonymous_Click(Sender: TObject);
Const k_recursive_count= 10000;
Var l_c_procedure_timer: c_procedure_timer;
l_ap: t_ap; l_ap_const: t_ap_const;
Begin l_c_procedure_timer:= c_procedure_timer.Create;
Try l_c_procedure_timer.m_initial_iterations:= 100;
l_c_procedure_timer.m_iterations:= 100; l_ap:=
Procedure (p_ap: t_ap; p_count: Integer)
Begin
If p_count> 0
Then p_ap(p_ap, p_count- 1);
End; l_ap_const:=
Procedure (Const pk_ap: t_ap_const; p_count: Integer)
Begin
If p_count> 0
Then pk_ap(pk_ap, p_count- 1);
End; display('');
l_c_procedure_timer.measure_time('anon(p_anon, n)',
Procedure Begin
l_ap(l_ap, k_recursive_count);
End );
l_c_procedure_timer.measure_time('anon(const p_anon, n)',
Procedure Begin
l_ap_const(l_ap_const, k_recursive_count);
End ); Finally
l_c_procedure_timer.Free; End;
End; // measure_anonymous_Click | - et son résultat :
Quelques remarques
5.2 - Enumerators et Anonymes
5.2.1 - Enumérateur classique Les énumérateurs sont définis par les éléments suivants: - mon_c_xxx.GetEnumerator fournit un objet énumérateur permettant d'accéder à tous les éléments de mon_c_xxx
- mon_c_enumerateur.MoveNext qui est True si la liste contient encore des éléments
- mon_c_enumerateur.Current retourne l'élément courant
Voici un énumérateur explicitement dédié aux entiers:
Type c_integer_sequence_enumerator=
Class Private
FCurrent: Integer;
FCount: Integer;
FIncrement: Integer;
Function GetCurrent: Integer;
Public
Constructor Create(Start, Increment, Count: Integer);
Function MoveNext: Boolean;
Property Current: Integer read GetCurrent;
End; // c_integer_sequence_enumerator
Constructor c_integer_sequence_enumerator.Create(Start, Increment, Count: Integer);
Begin FCurrent:= Start;
FCount:= Count; FIncrement:= Increment;
End; // Create
Function c_integer_sequence_enumerator.GetCurrent: Integer;
Begin Result:= FCurrent;
End; // GetCurrent
Function c_integer_sequence_enumerator.MoveNext: Boolean; Begin
If FCount<= 0
Then Exit(False);
Inc(FCurrent, FIncrement); Dec(FCount);
Result:= True; End; // MoveNext |
et son utilisation:
Procedure TForm1.integer_sequence_enumerator_Click(Sender: TObject);
Begin
With c_integer_sequence_enumerator.create(10, 3, 5) Do
Begin While MoveNext Do
display(IntToStr(Current)); Free;
End; // with c_integer_sequence_enumerator
End; // integer_sequence_enumerator_Click |
5.2.2 - Utilisation d'une Interface
Pour avoir un énumérateur lié à des Classes, nous pouvons définir des Interface - pour l'énumérateur
- pour la fonction GetEnumerator qui fournit l'enumerator
Pour énumérer une structure quelconque, comme une liste de c_person, il suffit alors - de créer une Classe qui implémente un énumérateur de notre structure de personnes
- d'implémenter iEnumerable pour notre liste de personnes.
Ce qui peut se représenter par le diagramme de classe UML suivant :
Donc
- voici nos Interfaces
Type i_enumerator<T> = Interface
Function GetCurrent: T;
Function MoveNext: Boolean;
Procedure Reset;
Property Current: T read GetCurrent;
End; // i_enumerator<T>
i_enumerable<T> = Interface
Function GetEnumerator: i_enumerator<T>;
End; // i_enumerable<T> | - notre liste de personne et leur énumérateur
Type c_person_list_2=
Class(tInterfacedObject, i_enumerable<c_person>)
m_person_array: Array Of c_person;
m_person_count: integer;
Constructor create_person_list_2;
Procedure add_person(p_c_person: c_person);
// -- i_enumerable
Function GetEnumerator: i_enumerator<c_person>;
Destructor Destroy; Override;
End; // c_person_list_2 c_person_enumerator_2=
Class(tInterfacedObject, i_enumerator<c_person>)
m_c_person_list_ref: c_person_list_2;
m_current_index: integer;
Constructor create_person_enumerator_2(p_c_person_list_ref: c_person_list_2);
// -- i_enumerator
Function GetCurrent: c_person;
Function MoveNext: Boolean;
Procedure Reset;
Property Current: c_person read GetCurrent;
End; // c_person_enumerator_2
Constructor c_person_enumerator_2.create_person_enumerator_2(p_c_person_list_ref: c_person_list_2);
Begin m_c_person_list_ref:= p_c_person_list_ref;
m_current_index:= -1; End; // create_person_enumerator_2
Function c_person_enumerator_2.GetCurrent: c_person; Begin
If m_current_index< m_c_person_list_ref.m_person_count
Then Result:= m_c_person_list_ref.m_person_array[m_current_index]
Else Raise Exception.Create('reached_end');
End; // GetCurrent
Function c_person_enumerator_2.MoveNext: Boolean; Begin
Inc(m_current_index);
Result:= m_current_index< m_c_person_list_ref.m_person_count;
End; // MoveNext Procedure c_person_enumerator_2.Reset;
Begin m_current_index:= -1; End; // Reset
Constructor c_person_list_2.create_person_list_2; Begin
SetLength(m_person_array, 16); End; // create_person_list_2
Function c_person_list_2.GetEnumerator: i_enumerator<c_person>;
Begin
Result:= c_person_enumerator_2.create_person_enumerator_2(Self);
End; // GetEnumerator
Procedure c_person_list_2.add_person(p_c_person: c_person);
Begin
If m_person_count= Length(m_person_array)
Then SetLength(m_person_array, 2* m_person_count);
m_person_array[m_person_count]:= p_c_person;
Inc(m_person_count); End; // add_person
Destructor c_person_list_2.Destroy;
Var l_person_index: Integer; Begin
For l_person_index:= 0 To m_person_count- 1 Do
m_person_array[l_person_index].Free;
m_person_array:= Nil; Inherited;
End; // Destroy | - et notre utilisation:
Procedure TForm1.person_list_i_enumerator_Click(Sender: TObject);
Var l_i_enumerator: i_enumerator<c_person>;
l_c_current_person: c_person; Begin
With c_person_list_2.create_person_list_2 Do
Begin
add_person(c_person.create_person('smith', 33));
add_person(c_person.create_person('joyce', 44));
add_person(c_person.create_person('allen', 55));
l_i_enumerator:= GetEnumerator;
While l_i_enumerator.MoveNext Do
With l_i_enumerator.Current As c_person Do
display(f_display_person); Free;
End; // with c_person_list
End; // person_list_i_enumerator_Click |
Notez que
- le programme .ZIP contient aussi une version non générique qui utilise iEnumerator et iEnumerable
- ces Interfaces sont définies dans SYSTEM.PAS, et ont même des descendants génériques :
mais nous ne sommes jamais arrivés à utiliser ces énumérateurs génériques. D'aucuns mettent en cause le fait que iEnumerator.GetCurrent soit un
tObject et iEnumerator<T> soit un T. - mentionnons aussi qu'avec notre c_person_list(iEnumerator), nous avons pu écrire
Var l_i_enumerator: iEnumerator;
l_c_current_person: c_person;
With c_person_list.create_person_list Do
// ooo
While l_i_enumerator.MoveNext Do
With l_i_enumerator.Current As c_person Do
display(f_display_person); | alors que l'Interface ne contient aucun GUID, qui est normalement
nécessaire pour As - par conséquent, nous avons donc préféré utiliser nos propres Interfaces
5.2.3 - Liste Filtrée Pour filtrer une liste, il suffit de la doter d'un énumérateur en aménageant
MoveNext pour n'accepter que les éléments satisfaisant une condition C'est là qu'interviennent les méthodes anonymes: il suffit de passer à la liste le filtre sous forme d'une fonction anonyme.
Au lieu de reprendre notre liste de personnes, nous allons définir une liste générique ayant cette possibilité de filtrage.
Les tList<T> sont déjà dotées d'énumérateurs. Toutefois
- l'énumérateur en question est défini dans GENERICS.COLLECTIONS et cette Classe n'a rien à voir avec les Interfaces de SYSTEM.PAS
- Delphi a prévu que nous puissions changer d'énumérateur en surtypant
DoGetCurrent et DoMoveNext.
Voici le diagramme de classe UML correspondant :
Au niveau du code - notre énumérateur avec son filtre :
Type t_af_filter<T> = Reference To Function (p_T: T): boolean;
c_filtered_enumerator<T>=
Class( TEnumerator<T> )
Private
m_c_base_list: TList<T> ;
m_af_filter: t_af_filter< T>;
m_current_index: Integer;
Function GetCurrent: T;
Function f_accept_T: Boolean;
Protected
Function DoGetCurrent: T; Override;
Function DoMoveNext: Boolean; Override;
Public
Constructor create_filtered_enumerator(p_c_base_list: tList< T> ;
p_af_filter: t_af_filter< T> );
Function MoveNext: Boolean;
Property Current: T read GetCurrent;
End; // c_filtered_enumerator
Constructor c_filtered_enumerator<T>.create_filtered_enumerator(
p_c_base_list: TList<T>;
p_af_filter : t_af_filter<T>); Begin
Inherited Create; m_c_base_list:= p_c_base_list;
m_af_filter:= p_af_filter; m_current_index:= - 1;
End; // create_filtered_enumerator
Function c_filtered_enumerator<T>.DoMoveNext: Boolean;
Begin Result := MoveNext;
End; // DoMoveNext
Function c_filtered_enumerator<T>.GetCurrent: T;
Begin Result := m_c_base_list[m_current_index];
End; // GetCurrent
Function c_filtered_enumerator<T>.f_accept_T: Boolean;
Begin Result := True;
If Assigned(m_af_filter)
Then Result := m_af_filter(m_c_base_list[m_current_index]);
End; // f_accept_T
Function c_filtered_enumerator<T>.MoveNext: Boolean;
Begin
If m_current_index= m_c_base_list.Count - 1
Then Exit(False); Repeat
Inc(m_current_index);
Until (m_current_index>= m_c_base_list.Count) Or f_accept_T;
Result := m_current_index< m_c_base_list.Count;
End; // MoveNext
Function c_filtered_enumerator<T>.DoGetCurrent: T;
Begin Result := GetCurrent;
End; // DoGetCurrent | - voici notre liste générique avec filtrage:
Type c_filtered_list<T: Class> =
Class( TObjectList<T> )
Private
m_c_filtered_enumerator: c_filtered_enumerator<T>;
Public
Procedure set_filtered_enumerator(
p_c_filtered_enumerator: c_filtered_enumerator<T>);
Function GetEnumerator: c_filtered_enumerator<T>; Reintroduce;
End;
Function c_filtered_list<T>.GetEnumerator: c_filtered_enumerator<T>;
Begin Result:= m_c_filtered_enumerator;
End; // GetEnumerator
Procedure c_filtered_list<T>.set_filtered_enumerator(
p_c_filtered_enumerator: c_filtered_enumerator<T>); Begin
m_c_filtered_enumerator:= p_c_filtered_enumerator;
End; // set_enumerator_filter | - et un exemple d'utilisation
Var g_c_person_list: c_filtered_list<c_person>= Nil;
// ooo initialise la liste
Procedure TForm1.filtered_list_Click(Sender: TObject);
Var l_c_filter_person_enumerator: c_filtered_enumerator<c_person>;
l_c_person: c_person; Begin
l_c_filter_person_enumerator:= c_filtered_enumerator<c_person>.
create_filtered_enumerator( g_c_person_list,
Function(p_c_person: c_person): Boolean
Begin
Result:= p_c_person.m_first_name> filter_edit_.Text
End) ;
g_c_person_list.set_filtered_enumerator(l_c_filter_person_enumerator);
For l_c_person In g_c_person_list Do
display(l_c_person.f_display_person);
End; // filtered_list_Click |
Notez que
- la fonction f_accept_T ne PEUT PAS être nichée dans MoveNext (E2570)
- nous avons bien utilisé un FOR IN, qui fonctionne parce que nous avons un énumérateur
5.3 - Anonymes et Threads
5.4 - Synchronize Les threads doivent utiliser Synchronize pour pouvoir accéder à des contrôles visuels de la VCL. Synchronize a comme paramètre une procédure sans paramètre de t_my_thread. Et
si nous souhaitons accéder à des locales de t_my_thread.Execute, il faut auparavant les sauvegarder comme "variables mailbox" dans t_my_thread Schématiquement nous avons
ou, au niveau code: Type c_my_thread=
Class(tThread) Private
m_index: Integer;
Public
Procedure do_display;
Procedure Execute; Override;
End; // c_my_thread
Procedure c_my_thread.do_display; Begin
Form1.Memo1.Lines.Add(IntTostr(m_index));
End; // do_display Procedure c_my_thread.Execute;
Var l_index: Integer; Begin
For l_index:= 1 To 10 Do
Begin m_index:= l_index;
Synchronize(do_display); End; // for l_index
End; // Execute |
5.5 - Synchronize(tProc)
tThread a été doté en Delphi 2009 d'un autre méthode Syncrhonize qui accepte un paramètre de type procédure anonyme:
Type TThreadMethod= Procedure Of Object;
TThreadProcedure= Reference To Procedure;
TThread= Class
Procedure Synchronize(AMethod: TThreadMethod); Overload;
Procedure Synchronize(AThreadProc: TThreadProcedure); Overload;
End; // TThread |
L'exemple précédent pourrait donc devenir:
Type c_my_thread=
Class(tThread) Public
Procedure Execute; Override;
End; // c_my_thread
Procedure c_my_thread.Execute;
Var l_index: Integer; Begin
l_index:= 1; Repeat Synchronize(
Procedure Begin
Form1.Memo1.Lines.Add(IntTostr(l_index));
End); Inc(l_index);
Until l_index> 10; End; // Execute |
5.6 - Téléchargement et traitements Cet exemple montre comment utiliser des threads pour télécharger des pages Web et effectuer un traitement sur ces pages, comme la sauvegarde, l'extraction de liens etc
- voici la Classe qui télécharge une page dont nous fournissons l'URL, et appelle une procédure anonyme pour effectuer un traitement sur cette page:
Type t_ap_process_donwload= Reference To Procedure (p_c_response_content_stream: TStringStream);
c_download_thread= Class(TThread)
Private
m_url: string;
m_ap_process_donwload: t_ap_process_donwload;
Protected
Procedure Execute; Override;
Public
Constructor create_download_thread(Const p_url: string;
p_handle_did_terminate: tNotifyEvent;
p_ap_process_donwload: t_ap_process_donwload);
End; // c_download_thread
Constructor c_download_thread.create_download_thread(Const p_url: string;
p_handle_did_terminate: tNotifyEvent;
p_ap_process_donwload: t_ap_process_donwload); Begin
m_url:= p_url; m_ap_process_donwload:= p_ap_process_donwload;
OnTerminate:= p_handle_did_terminate; FreeOnTerminate:= True;
Inherited Create(False);
End; // create_download_thread
Procedure c_download_thread.Execute;
Var l_c_id_http: TIdHttp;
l_c_response_content_stream: TStringStream; Begin
Synchronize(Procedure Begin
display('> url '+ m_url);
End);
l_c_id_http:= TIdHTTP.Create(Nil);
l_c_response_content_stream:= TStringStream.Create; Try
l_c_id_http.Get(m_url, l_c_response_content_stream);
l_c_response_content_stream.Position:= 0;
m_ap_process_donwload(l_c_response_content_stream); Finally
l_c_response_content_stream.Free; End;
l_c_id_http.Free; Synchronize(Procedure
Begin
display('< url '+ m_url);
End); End; // Execute |
- voici une utilisation pour afficher le texte .HTML dans un memo:
Procedure TForm1.display_html_text_Click(Sender: TObject);
Begin display('> display_page');
With c_download_thread.create_download_thread(
url_edit_.Text, handle_thread_did_terminate,
Procedure (p_c_response_content_stream: TStringStream)
Begin
display(' '+ p_c_response_content_stream.DataString);
End ) Do;
display('< display_page'); End; // display_html_text_Click |
Nous pouvons aussi appeler d'autres procédures qui effectuent des traitements sur le contenu d'une page. Voici comment extraire les liens: - la méthode d'extraction est la suivante:
Type t_ap_process_found_item= Reference To Procedure (Const strLink: string);
Procedure extract_content_links(p_content_string: string;
p_ap_process_found_item: t_ap_process_found_item);
Procedure extract_content_links(p_content_string: string;
p_ap_process_found_item: t_ap_process_found_item);
// -- extract all "<A HREF="http://xxx">
Var l_href_start_index, l_href_end_index: Integer;
l_link_url: string; Begin
display('> extract_content_links');
p_content_string:= LowerCase(p_content_string);
l_href_start_index:= 1; Repeat
l_href_start_index:= PosEx('href="http', p_content_string, l_href_start_index);
If l_href_start_index<> 0
Then Begin
l_href_start_index:= l_href_start_index+ 6;
l_href_end_index:= PosEx('"', p_content_string, l_href_start_index);
l_link_url:= Copy(p_content_string, l_href_start_index,
l_href_end_index- l_href_start_index);
// -- display or whatever this link
p_ap_process_found_item(l_link_url);
l_href_start_index:= l_href_end_index+ 1;
End; Until l_href_start_index= 0;
display('< extract_content_links'); End; // extract_content_links |
- et voici un exemple d'utilisation:
Procedure TForm1.extract_and_display_links_Click(Sender: TObject);
Begin With c_download_thread.create_download_thread(
url_edit_.Text, handle_thread_did_terminate,
Procedure (p_c_response_content_stream: TStringStream)
Begin
extract_content_links(p_c_response_content_stream.DataString,
Procedure (Const p_link_url: string)
Begin
Memo1.Lines.Add(p_link_url);
End );
End ) Do;
End; // extract_and_display_links_Click |
Notez que
- la procédure d'extraction des liens est aussi dotée d'une procédure anonyme. Ici, le clic du bouton demande l'affichage, mais nous aurions aussi bien pu demander le stockage, le filtrage, la récursion du chargement etc
5.7 - Autres exemples Parmi les autres exemples citons - les énumérateurs de tout poil (crible d'Eratosthène pour les nombres premiers)
- la programmation fonctionnelle (Map, Reduce etc)
- les chaînages de traitements, parallèles ou non. "Multicast Events" qui vise à simuler les delegates en est un exemple
Ces exemples sont relativement longs ce qui explique que nous n'avons pu les présenter
6 - Commentaires 6.1 - Complexité Les génériques évitent la création d'une fonction explicite, et permettent souvent de définir le traitement au moment de l'appel d'une procédure.
Cela ressemble un peu à "Inversion of Control" : la procédure ayant le paramètre anonyme ne sait pas du tout quel traitement de paramètre effectuera. C'est l'appelant qui spécifie ce qui sera calculé.
En revanche, certains exemples ont aussi montré que le style peut aussi apparaître plus complexe, plus difficile à comprendre et relire. Question d'habitude peut être.
6.2 - Encapsulation
Nous avons aussi rencontré lors de nos explorations, des exemples d'encapsulation qui nous paraissent un peu extrèmes. Travaillant surtout en Delphi 6, nos Classes ne contiennent que des attributs et des méthodes.
En fait, depuis quelques versions déjà, nous pouvons inclure dans une Classe - des Const
- des Type
- des Var
En fait, la Classe devient en quelque sorte un mini bloc imbriqué.
En Pascal, la structure de base est en-tête / bloc : Schématiquement nous avons
Program compute; Const Type
Var Procedure process; Const
Type Var Begin
End; Begin End. |
Et pour les Classes, Barry KELLY a utilisé des CONST et VAR imbriqués:
Type TBenchmarker = Class
Private
Const DefaultIterations = 3;
DefaultWarmups = 1;
Var FReportSink: TProc<string,Double>;
FWarmups: Integer;
FIterations: Integer;
FOverhead: Double;
Class Var FFreq: Int64;
Class Procedure InitFreq;
Public
Constructor Create(Const AReportSink: TProc<string,Double>);
Class Function Benchmark(Const Code: TProc;
Iterations: Integer = DefaultIterations;
Warmups: Integer = DefaultWarmups): Double; Overload;
Procedure Benchmark(Const Name: string; Const Code: TProc); Overload;
Function Benchmark<T>(Const Name: string; Const Code: TFunc<T>): T; Overload;
Property Warmups: Integer read FWarmups write FWarmups;
Property Iterations: Integer read FIterations write FIterations;
End; // TBenchmarke |
et Malcolm GROVES a utilisé un énumérateur sous forme de Classe imbriquée :
Type TFilteredList<T> =
Class( TList<T> )
Private
FFilterFunction: TFilterFunction< T> ;
Public
Type TFilteredEnumerator=
Class( TEnumerator<T> )
Private
FList: TFilteredList<T> ;
FIndex: Integer;
FFilterFunction: TFilterFunction< T> ;
Function GetCurrent: T;
Protected
Function DoGetCurrent: T; Override;
Function DoMoveNext: Boolean; Override;
Function IsLast: Boolean;
Function IsEOL: Boolean;
Function ShouldIncludeItem: Boolean;
Public
Constructor Create(AList: TFilteredList< T> ;
AFilterFunction: TFilterFunction< T> );
Property Current: T read GetCurrent;
Function MoveNext: Boolean;
End; // TFilteredEnumerator
Procedure SetFilter(AFilterFunction: TFilterFunction< T> );
Function GetEnumerator: TFilteredEnumerator; Reintroduce;
Procedure ClearFilter;
End; // TFilteredList |
Ces nouvelles possibilités augmentent l'encapsulation, mais compliquent peut-être un peu la lecture.
7 - Télécharger le code source Delphi Vous pouvez télécharger:
Comme d'habitude:
- nous vous remercions de nous signaler toute erreur, inexactitude ou problème de téléchargement en envoyant un e-mail à jcolibri@jcolibri.com. Les corrections
qui en résulteront pourront aider les prochains lecteurs
- tous vos commentaires, remarques, questions, critiques, suggestion d'article, ou mentions d'autres sources sur le même sujet seront de même
les bienvenus à jcolibri@jcolibri.com.
- plus simplement, vous pouvez taper (anonymement ou en fournissant votre e-mail pour une réponse) vos commentaires ci-dessus et nous les envoyer en
cliquant "envoyer" :
- et si vous avez apprécié cet article, faites connaître notre site,
ajoutez un lien dans vos listes de liens ou citez-nous dans vos blogs ou réponses sur les messageries. C'est très simple: plus nous aurons de visiteurs et de références Google, plus nous écrirons d'articles.
8 - Références Citons - Barry KELLY, qui a codé les génériques et les anonymes:
- Allen BAUER - Delphi Chief Scientist
Pour les génériques, vous pouvez vous reporter à notre article - Génériques Delphi : exemple avec une tList<T>,
création d'une pile, règles de compatibilité de type, génération du code, types pouvant être génériques, contraintes Interface, Class, héritage, Constructor. Exemple Observateur et Calculateur. Interfacees et
conteneurs génériques de la Vcl
9 - L'auteur John COLIBRI est passionné par le développement Delphi et les applications de Bases de Données. Il a écrit de nombreux livres
et articles, et partage son temps entre le développement de projets (nouveaux projets, maintenance, audit, migration BDE, migration Xe_n,
refactoring) pour ses clients, le conseil (composants, architecture, test) et la
formation. Son site contient des articles
avec code source, ainsi que le programme et le calendrier des stages de formation Delphi, base de données, programmation objet, Services Web, Tcp/Ip et
UML qu'il anime personellement tous les mois, à Paris, en province ou sur site client. |