|
|
Les méthodes anonymes de C# v2 par Patrick Smacchia |
Introduction
aux méthodes anonymes de C#2
Méthodes
anonymes et arguments
Les
méthodes anonymes et la généricité
Exemples
simples d’utilisation des méthodes anonymes
Le
compilateur C#2 et les méthodes anonymes
Une
méthode anonyme accède à une variable locale de la méthode qui
l’encapsule
Variable
locale capturée et complexité du code.
Une
méthode anonyme accède à un argument de la méthode qui l’encapsule
Une
méthode anonyme accède à un membre de la classe qui définie la méthode qui
l’encapsule
Exemples
avancés d’utilisation des méthodes anonymes
Définitions:
fermeture (closure en anglais) et environnement lexical
Un
peu plus loin dans la compréhension des fermetures
· Une bonne connaissance générale de C#1.
· Les délégués de .NET1 Dans cet article, nous nommerons une classe déléguée une délégation. Rappelons qu’une délégation est définie avec le mot-clé C# delegate et dérive automatiquement et implicitement de la classe System.Delegate. L’instance d’une délégation sera nommée un délégué. Rappelons aussi qu’un délégué sert à référencer et à invoquer une ou plusieurs méthodes. Rappelons aussi que si un délégué référence une méthode d’instance, il encapsule aussi une référence vers l’instance sur laquelle la méthode doit être invoquée.
Les génériques sont, à raison, présentés comme la fonctionnalité phare des langages de .NET v2. En ‘surfant’ hors des sentiers battus, je me suis aperçu que les nouvelles fonctionnalités de méthodes anonymes et d’itérateurs sont elles aussi très intéressantes. Contrairement aux génériques, ces deux fonctionnalités n’impliquent aucune nouvelle instruction IL. Toute la magie se situe au niveau du compilateur. Je vous propose une série de deux articles pour vous présenter ces trouvailles, d’abord un article sur les méthodes anonymes puis un autre sur les itérateurs. Vous comprendrez que c’est l’ordre logique d’apprentissage. Chacun des deux articles aura la même structure : une présentation ‘classique’ de la fonctionnalité suivie de l’analyse du travail du compilateur pour enfin commenter des utilisations avancées (détournées ?!).
Commençons par un premier exemple de réécriture de
code C#1 au moyen des méthodes anonymes de C#2. Voici du code compilable en C#1 qui montre comment référencer une méthode
au travers d’un délégué :
using System;
class Program{
delegate void
DelegateType();
static DelegateType
GetMethod(){
return new
DelegateType(MethodBody);
}
static void
MethodBody(){
Console.WriteLine("Hello");
}
static void
DelegateType delegateInstance =
GetMethod();
delegateInstance();
delegateInstance();
Console.ReadKey();
}
}
Voici le même code réécrit en C#2 au moyen
d’une méthode anonyme:
using System;
class Program{
delegate void
DelegateType();
static DelegateType
GetMethod(){
return
delegate(){Console.WriteLine("Hello");};
}
static void
DelegateType delegateInstance =
GetMethod();
delegateInstance();
delegateInstance();
Console.ReadKey();
}
}
Plusieurs remarques s’imposent :
· Le mot-clé delegate a une nouvelle utilisation en C#2. Il est utilisé au sein du corps d’une méthode pour indiquer au compilateur qu’il doit s’attendre à trouver le corps d’une méthode anonyme.
· On constate que l’on peut assigner une méthode anonyme à un délégué.
· On comprend le nom de méthode anonyme pour cette nouvelle fonctionnalité : La méthode définie au sein de GetMethod() n’a effectivement pas de nom. On peut cependant l’invoquer car elle est référencée par un délégué.
Notez aussi que la syntaxe d’assignement de plusieurs méthodes à un délégué avec l’opérateur += est utilisable avec les méthodes anonymes :
using System;
class Program{
delegate void
DelegateType();
static void
DelegateType delegateInstance = delegate() { Console.WriteLine("Hello"); };
delegateInstance += delegate() { Console.WriteLine("Bonjour"); };
delegateInstance();
Console.ReadKey();
}
}
Comme l’on peut s’en douter ce programme affiche :
Hello
Bonjour
Comme le montre l’exemple suivant, une méthode anonyme peut avoir des arguments. Tous les types sont applicables, y compris la possibilité de passer des arguments par référence avec le mot-clé ref et des arguments de sortie avec le mot-clé out :
using System;
class Program{
delegate int DelegateType(int valTypeParam, string
refTypeParam, ref int
refParam, out int
outParam);
static DelegateType
GetMethod(){
return delegate(int
valTypeParam, string refTypeParam,ref int refParam,out int outParam)
{
Console.WriteLine("Hello valParam:{0} refTypeParam:{1}",
valTypeParam, refTypeParam);
refParam++;
outParam = 9;
return valTypeParam;
};
}
static void
DelegateType delegateInstance =
GetMethod();
int refVar = 5;
int outVar;
int
i = delegateInstance(1, "un", ref refVar, out
outVar);
int j = delegateInstance(2, "deux", ref
refVar, out outVar);
Console.WriteLine("i:{0} j:{1} refVar:{2} outVar:{3}", i,
j, refVar, outVar);
Console.ReadKey();
}
}
Ce programme affiche :
Hello valParam:1 refTypeParam:un
Hello valParam:2 refTypeParam:deux
i:1 j:2 refVar:7 outVar:9
Notez cependant que le type de la valeur de retour n’est pas définie par la méthode anonyme, mais par le type du délégué auquel elle est assignée. Celui-ci est toujours défini car le compilateur oblige à assigner toutes méthodes anonymes à un délégué dont le type est connu à la compilation. On en conclut qu’on ne peut assigner une méthode anonyme à un délégué référencé par une référence de type System.Delegate.
Notez aussi que la syntaxe C# avec le mot clé param n’est pas utilisable dans la liste d’arguments d’une méthode anonyme :
using System;
class Program{
delegate void
DelegateType(params
int[] arr);
static DelegateType
GetMethod(){
return delegate(params int[] arr){ // erreur de compilation: param is not valid in this context
Console.WriteLine("Hello");
};
}
//...
}
La raison pour laquelle le mot clé param n’est pas utilisable dans la liste d’arguments d’une méthode anonyme est simple : en interne le mot clé param oblige le compilateur a marquer la méthode concernée avec l’attribut ParamArrayAttribute. Or, les méthodes anonymes ne peuvent supporter d’attribut.
Lorsque le mot-clé delegate est utilisé sans parenthèse lors
de la définition d’une méthode anonyme sans argument d’entrée ni
de retour, celle-ci peut être assignée à n’importe quel type de délégué.
Bien entendu, le corps de la méthode anonyme ne peut alors pas utiliser les
paramètres d’entrée. En outre, une telle méthode anonyme ne peut rien
retourner. En conséquence, cette syntaxe n’est pas utilisable si la
signature du délégué cible retourne une valeur ou prend un paramètre out :
using System;
class Program{
delegate void
DelegateType(int
valTypeParam, string refTypeParam, ref int refParam);
static void
DelegateType delegateInstance = delegate{Console.WriteLine("Hello");};
int refVar = 5;
delegateInstance(1, "un",
ref refVar);
delegateInstance(2,
"deux", ref
refVar);
Console.ReadKey();
}
}
Une méthode anonyme peut avoir des arguments de type générique, comme le montre l’exemple suivant :
using System;
class UneClasse<T>{
delegate void DelegateType(T t);
internal void
UneMethode(T t){
DelegateType delegateInstance = delegate(T arg){Console.WriteLine("Hello arg:{0}",arg.ToString());};
delegateInstance(t);
}
}
class Program{
static void
UneClasse<double>
inst = new UneClasse<double>();
inst.UneMethode(5.5);
Console.ReadKey();
}
}
En C#2, les délégations peuvent admettre des arguments génériques. Un délégué instance d’une telle délégation peut référencer une méthode anonyme. Il faut alors résoudre les types génériques lors de la définition de la méthode anonyme, comme le montre l’exemple suivant :
using System;
class Program{
delegate void
DelegateType<T>(T t);
static void
DelegateType<double> delegateInstance = delegate(double arg) { Console.WriteLine("Hello arg:{0}", arg.ToString()); };
delegateInstance(5.5);
Console.ReadKey();
}
}
L’utilisation de méthodes anonymes est particulièrement
adaptée à la définition de ‘petites’ méthodes destinées à être invoquées
au moyen d’un délégué. Par exemple, vous pouvez définir au moyen
d’une méthode anonyme la procédure sur laquelle démarrera un nouveau thread :
using System;
using System.Threading;
class Program{
static void
Thread thread = new Thread(delegate(){
Console.WriteLine("ThreadHashCode:{0} Hello",Thread.CurrentThread.GetHashCode());
});
thread.Start();
Console.WriteLine("ThreadHashCode:{0} Bonjour", Thread.CurrentThread.GetHashCode());
Console.ReadKey();
}
}
Ce programme affiche :
ThreadHashCode:1
Bonjour
ThreadHashCode:3
Hello
Vous pouvez aussi définir au moyen d’une
méthode anonyme toute sorte de call-back, comme la réponse aux évènements des
contrôles Windows Forms:
public class UneForm : Form{
Button m_Button;
public UneForm(){
InitializeComponent();
m_Button.Click += delegate(object
sender, EventArgs args){
MessageBox.Show("m_Button Clicked");
};
}
void InitializeComponent()
{...}
}
Si l’utilisation des méthodes anonymes ne se
cantonnait qu’à ces exemples simples, le sujet n’aurait
certainement pas mérité un article sur DNG. Entrons maintenant dans les arcanes du
compilateur C#2 pour comprendre toute la puissance de ce mécanisme.
Comme l’on peut s’en douter, lorsque le
compilateur C#2 rencontre une méthode anonyme, il crée une méthode dans la
classe de la méthode qui l’encapsule :
using System;
class Program{
delegate void
DelegateType();
static void
DelegateType delegateInstance = delegate() { Console.WriteLine("Hello"); };
delegateInstance();
Console.ReadKey();
}
}
Ainsi le compilateur C#2 produit l’assemblage suivant à partir du code précédent (l’assemblage est visualisé avec l’outil Reflector de Lutz Roeder qui supporte déjà les assemblages de .NET2) :

On vérifie bien qu’une méthode privée, statique et nommée <Main>b__0() a été créée dans la classe encapsulante (i.e la classe Program) et contient le code de notre méthode anonyme. Une méthode anonyme est une méthode d’instance si elle est définie dans le corps d’une méthode d’instance.
On note aussi que cette méthode est référencée par le délégué <>9_CachedAnonymousMethodDelegate1, instance de DelegateType, qui est un champ statique de la classe encapsulante.
Il est intéressant de remarquer que le nom de cette méthode contient une paire de <>. De ce fait le compilateur ne vous autorise pas à l’invoquer directement à partir de votre code et l’intellisense ne l’affiche pas. En revanche le CLR est tout à fait capable d’utiliser une méthode avec un tel nom.
Pour ne pas compliquer notre exposé, nous
n’avons pas encore mentionné le fait qu’une méthode anonyme a la
possibilité d’accéder à une variable locale de la méthode qui
l’encapsule. Et c’est bien là que les choses commencent à être
intéressantes. Analysons l’exemple suivant:
using System;
class