Astuces DotNet (Sébastien Courtois)

09/06/2010

[.NET 3.5] Attention lors de l’utilisation des lambdas/closures

Filed under: .NET, C#, C# 4, Hors Catégorie, Intermediaire — Étiquettes : , , , , — sebastiencourtois @ 13:05

Afin de commencer cette article, commençons par une petite devinette.

Quel sont les résultats de ces deux morceaux de code :

var actions = new List<Action>();
for (int i = 0; i < 10; i++)
    actions.Add(() => Console.WriteLine(i));

foreach (var action in actions)
    action();
var actions = new List<Action>();

for (int i = 0; i < 10; i++)
{
    int j = i;
    actions.Add(() => Console.WriteLine(j));
}

foreach (var action in actions)
    action();

La première réponse que vous avez du vous faire est que ces deux codes ont le même résultat : afficher les nombres de 0 à 9. En effet, on met des méthodes délégués dans une liste de délégués puis on parcourt cette liste afin d’exécuter le code de ces délégués. La seule différence entre les deux codes est l’utilisation d ‘une variable local j.

Et pourtant la réponse est :

  • Exemple 1 : 10 10 10 10 10 10 10 10 10 10
  • Exemple 2 : 0 1 2 3 4 5 6 7 8 9

Autre exemple dans le même genre :

var actions = new List<Action>();
string[] urls = 
{ 
   "http://www.url.com", 
   "http://www.someurl.com", 
   "http://www.someotherurl.com", 
   "http://www.yetanotherurl.com" 
};

for (int i = 0; i < urls.Length; i++)
{
    actions.Add(() => Console.WriteLine(urls[i]));
}
var actions = new List<Action>();
string[] urls = 
{ 
   "http://www.url.com", 
   "http://www.someurl.com", 
   "http://www.someotherurl.com", 
   "http://www.yetanotherurl.com" 
};

for (int i = 0; i < urls.Length; i++)
{
    int j = i;
    actions.Add(() => Console.WriteLine(urls[j]));
}
Résultat : IndexOutOfRangeException (Index was outside the

bounds of the array
Résultat : les liens du tableaux sont affichés
  • Les lambdas peuvent être nuire gravement à la santé … mentale des développeurs 

Etrange non ? Décompilons le tout première exemple afin devoir ce qui est effectivement exécuté.

Note : Pour une raison inconnue, je n’ai pas réussi à obtenir ceci depuis Reflector. J’ai donc récupérer le code de l’article original.

[CompilerGenerated]
private sealed class <>c__DisplayClass2
{
    // Fields
    public int i;

    // Methods
    public void <Main>b__0()
    {
        Console.WriteLine(this.i);
    }
}
[...]
var actions = new List<Action>();

<>c__DisplayClass2 localFrame = new <>c__DisplayClass2();
for (localFrame.i = 0; localFrame.i < 10; localFrame.i++)
{
	actions.Add(localFrame.<Main>b__0);
}

foreach (var action in actions)
{
	action();
}

Comme on peut le voir, le fait de créer une lambda en utilisant une variable extérieure à celle ci à entrainer la génération d’une classe (<>c__DisplayClass2") qui est utilisé pour stocker la valeur de cette variable (dans la varaible i) et une méthode (<Main>b__0) qui contient le code de la lambda.  Si on regarde le code du premier for, on s’aperçoit que cette boucle incrémente la variable i de la classe DisplayClass2. Donc, à la fin de la première boucle, la classe DisplayClass2 contient une variable i = 10 et la liste actions contient une référence vers la méthode de cette classe. Ainsi lors de la boucle d’appel actions, on se retrouve a appeler le code Console.WriteLine(i) <=> Console.WriteLine(10) !!!!

Si on résonne de la même façon sur l’exemple 2, on obtient un appel Console.WriteLine(urls[4]) ==> Exception.

  • Pourquoi l’utilisation d’une variable locale résout le problème

Si on regarde le code ci dessous, on s’aperçoit que le problème vient du fait que la classe DisplayClass2 est créé en dehors de la boucle for et que celle ci utilise la variable i comme compteur. Le fait d’utiliser une variable temporaire à l’intérieur de la boucle for, oblige le compilateur à créer plusieurs instance de DisplayClass (une par itération). On obtient ainsi quelque chose comme le code suivant.

for (int idx = 0; idx < 10; idx++)

{

    <>c__DisplayClass2 localFrame = new <>c__DisplayClass2();

    localFrame.i = idx;

    actions.Add(localFrame.<Main>b__0);

}

Remarque : On pourrait penser que chaque instance est dispose lors de la sortie du scope. Cela n’’est pas vrai car la liste actiosn conserve une référence sur l’objet (en l’occurence sur une de ses méthodes) donc le garbage collector ne prend pas cet objet.

  • Conclusion

Les lambdas sont aujourd’hui utilisé dans de nombreuses technologies .NET (LINQ,PFX…) et sont une bonne alternative aux délégués classiques. Toutefois, on peut voir que cela peut engendré une certaine confusion voire des bugs lorsqu’on les utilise.

Je vous mets le lien vers l’article dont je me suis fortement inspiré pour cet article : Lambdas – Know your closures

05/03/2009

Ajouter des fonctionnalités à des classes du Framework .NET (ou des classes .NET dont vous n’avez pas le code source)

Filed under: .NET, Débutant — Étiquettes : , , , , , — sebastiencourtois @ 16:59

Au cours de projet, vous pouvez être amené à avoir besoin d’ajouter des fonctionnalités à une classe du Framework .NET. Lorsque vous n’avez pas le code source de la classe en question, .NET vous propose trois manières simples d’étendre les fonctionnalités d’une classe.

La méthode passe partout : L’héritage

L’héritage est la méthode classique. Utilisable dans tous les langages objet, ce principe permet de redéfinir une classe en créant une autre classe plus spécifique reprenant les fonctionnalités de la classe générale.

En C#, cela se fait en utilisant le caractère ‘:’.

public class A {}
public class C : A {}

Dans l’exemple ci-dessus, la classe C dérive de A. Ainsi toute ce qui est défini en public ou protected dans A sera utilisable dans C. En plus, C pourra définir de nouvelles propriétés/méthodes.

Même si l’héritage reste le meilleur moyen pour ajouter des fonctionnalités, il y a un cas (au moins) dans lesquels on ne peut pas l’utiliser :

  • La classe mère est scellé (mot clé sealed). Impossible de faire de l’héritage.

Dans ce cas, le Framework .NET 2.0 nous fourni une solution : La partialité.

La méthode .NET 2.0 : Partialité

Le principe de la partialité est de reprendre une classe en ajoutant, dans un autre fichier source, des propriétés et des méthodes à la classe.

Pour cela on utilise le mot clé partial.

(Remarque : les classes partielles doivent être dans le même namespace)

Fichier A.cs : public sealed class A {}

Fichier A2.cs : public partial class A {}

La partialité n’étant pas de l’héritage, vous n’avez pas de problème avec le mot clé sealed. L’avantage de cette méthode est que votre code source référencera toujours la classe A quelque soit le nombre de fonctionnalités que vous aurez ajoutées  (avec l’héritage, vous auriez du remplacer toutes les occurrences de la classe A par B).

La partialité est notamment très utilisé dans Visual Studio pour les Winforms / WPF (séparation du code métier/ code interface).

La méthode .NET 3.5 : Les méthodes d’extensions

Cette dernière méthode a été introduite avec le Framework .NET 3.5. Le principe est de créer des méthodes pour une classe qui vont s’ajouter à celle-ci. Ces nouvelles méthodes seront placées dans une classe utilitaire statique (namespace/nom de classe différents de la classe visée). Le choix de la classe à étendre se fera dans le premier paramètres de la méthode précédé du mot clé this. Les méthodes d’extensions sont toujours statiques.

Blog4-1 On peut ensuite l’utiliser de façon très simple :

Blog4-2 Vous pouvez noter que l’IntelliSense fournit un icône particulier pour les méthodes d’extensions. De plus, vous pouvez voir le commentaire (extension) dans le Tooltip.

Les méthodes d’extensions sont une des bases de LINQ.

03/03/2009

Débogage d’un processus .NET lancé par un processus externe

Filed under: .NET, Débutant — Étiquettes : , , , , , — sebastiencourtois @ 21:46

Au cours de mes temps libre, j’ai eu l’occasion de participer à un concours de programmation (TopCoder). Dans une des catégories, je devais créer un exécutable (.NET) qui était lancé dans un processus Java (fichier exécutable .jar).

Le soucis est que je voudrais pouvoir utiliser toutes les fonctionnalités de débogage du code source pendant l’exécution de mon programme.

Première solution : Lancer le processus externe après la compilation de l’exécutable :

En modifiant le projet .NET, on peut choisir de lancer un autre processus lors de la fin de la compilation de l’exécutable.

Pour accéder à ce menu : Clic droit sur le projet > Properties > Onglet “Debug”

Vous pouvez choisir entre :

  1. Ouvrir le projet courant et attacher le debugger dessus (Comportement par défaut)
  2. Ouvrir un autre exécutable et attacher le debugger sur le projet courant
  3. Ouvrir un navigateur Web sur une adresse définie (pour le débogage ASP.NET)

J’ai beaucoup utilisé la deuxième option lors d’Imagine Cup l’an passé (sur Project Hoshimi). Toutefois, cette option n’accepte que les exécutables avec une extension .exe (donc ni .bat ni .jar :().

Deuxième solution : Attacher un processus lancé à Visual Studio

Visual Studio permet de débogage n’importe quel processus lancé sur la machine.

Pour cela, il suffit d’aller dans le menu Debug > Attach To Process.

attachtoProcess

Une fenêtre apparait permettant de choisir le processus voulu parmi ceux tournant sur la machine.

blogattachtoprocesswindow

Une fois le processus sélectionné, vous avez deux possibilités :

  1. Vous possédez le code source de l’application : Dans ce cas, vous serez à même de déboguer en plaçant des points d’arrêts au sein du code source
  2. Vous ne possédez pas le code source : Dans ce cas, vous pourrez seulement trapper les exceptions sortants de l’applications et voir le code “désassemblé” (MSIL pour les codes managés / Assembleur pour les codes natifs).

Cette méthode est simple et pratique pour déboguer des applications tournant en boucle comme des services.

Dans mon cas, l’exécution de mon code était un algorithme très court. Je n’avais pas le temps d’aller chercher mon processus dans la fenêtre “Attach To Process” pour avoir accès au débuggeur.

Troisième solution : Attacher le debugger Visual Studio à un processus par code

Il faut donc trouver un moyen d’attacher le debugger Visual Studio au début du code de mon processus.

Une petite recherche sur MSDN m’a permis de trouver la classe System.Diagnostics.Debugger qui permet d’interagir avec le debugger Visual Studio au travers de mon code.

Cette classe présente assez peu de méthodes mais permet tout de même de poser des points d’arrêt, de loguer certaines informations et d’attacher un débugger.

La méthode Launch() est la méthode permettant d’attacher le debugger au processus courant. Elle est bloquante et entraine l’apparition d’une fenêtre demandant le debugger à utiliser.

Blog3

Il y a, en général, le choix d’ouvrir une nouvelle instance (dans ce cas, le débogage se fera avec le code MSIL/ASM) ou d’attacher le processus à un Visual Studio déjà ouvert (si possible contenant le code source du processus à déboguer). Je n’ai pas trouvé de moyen de supprimer cette étape de sélection par le code.

Une fois le debugger sélectionné, la phase de débogage se fait de la même manière que pour une application classique. Il est possible de vérifier si le processus est attaché à un debugger grâce à la propriété Debugger.IsAttached.

La méthode Break() permet de poser un point d’arrêt depuis le code source.

02/03/2009

Premier post de ce blog

Filed under: Hors Catégorie — Étiquettes : , , , , , , — sebastiencourtois @ 22:59

Bonjour à tous,

Je vais commencer par me présenter :

Je m’appelle Sébastien Courtois.

Je suis Consultant / Chef de projet .NET chez Winwise à Paris.Je fait partie du pôle RIDA (Rich Internet and Desktop Application). Pour résumer, je travaille régulièrement en Winforms, ASP.NET, Silverlight, WPF, ASP.NET MVC …

Afin de rendre ce blog unique, je vais essayer de publier un article/astuce par jour. Je ne sais pas si je tiendrais le rythme :).

Les articles et astuces seront principalement des cas pratiques tirés de ma propre expérience ou des expériences/questions d’autres personnes avec qui j’ai l’habitude de travailler (ils se reconnaitront surement assez vite). Ce ne seront souvent pas des exemples très poussés mais plus des petits problèmes quotidiens qui nous font perdre un temps précieux en projet.

En espérant que ce blog vous aidera et vous plaira. N’hésitez pas à m’envoyer des retours sur les erreurs ou les améliorations possibles (il y en aura surement :)).

Propulsé par WordPress.com.