ClearTrace, l'outil de Bill Graziano pour optimiser des requêtes SQL

Posté le mercredi 27 mai 2009 22:27 par jay :: 0 commentaire(s)
Classé sous ,
This post is also available in English.

Après avoir écouté le show #103 de RunAs Radio avec Bill Graziano, j'ai décidé de tester ClearTrace, un outil d'analyse de traces SQL.

Il se trouve qu'en ce moment, je suis dans une passe d'optimisation agressive de SQL, et le projet sur lequel je travaille a une séquence d'opérations qui prennent plus de 24 heures à compléter. Analyser manuellement les traces avec le profiler SQL est généralement coûteux en temps, comme de rechercher une aiguille dans une botte de foin, en particulier dans mon cas lorsque le log dépasse 7 Go.

Trouver de petites requêtes qui sont exécutées des milliers de fois est plutôt difficile, et trouver les candidats appropriés à l'optimisation encore plus complexe. On ne veut généralement pas perdre de temps à optimiser des requêtes qui n'ont que peu d'impact.

C'est ici que l'outil de Bill entre en jeu. On lui donne un fichier de trace, et l'outil l'analyse et donne des informations agrégées sur ce qui prend le plus de CPU/Temps/Lecture/Écriture. Faites votre choix.

Après quelques heures et exécution de ClearTrace pour trouver les procédures stockées qui nécessitaient des mises à jour, j'ai trouvé un jeu de procédures qui étaient exécutées des milliers de fois et utilisaient un cumul d'I/O important. Après avoir optimisé ces requêtes, le process au complet est maintenant descendu à environ 7 heures.

Pas de magie ici, la clé est simplement de trouver quoi optimiser dans des séquences qui exécutent des millions de requêtes. L'outil de Bill fait cela parfaitement !

Au passage, lorsque j'ai essayé ClearTrace pour la première fois, j'ai reçu une exception de type OutOfMemory en tentant d'importer mon gros fichier de trace. Il se trouve qu'après l'avoir exporté avec Reflector et debuggé un peu le code, j'ai trouvé une petite  erreur de refactoring que Bill a corrigé rapidement. Merci Bill !

Et comme Carl Franklin le dit dans la section "Better Know a Framework" de .NET Rocks : "learn it, use it, love it !"

L'extension method Traverse et un détour par F#

Posté le lundi 18 mai 2009 22:14 par jay :: 0 commentaire(s)
Classé sous , , ,
This post is also available in english.

De temps à autres, il arrive de croiser des structures de données qui prennent la forme de liste chaînées simple, comme par exemple la classe MethodInfo et sa méthode GetBaseDefinition.

Supposons que pour une méthode virtuelle on cherche, pour un type donné, quelle méthode surchargée dans la hiérarchie est marquée avec un attribut spécifique. J'assume pour cet exemple que l'attribut en question n'est pas héritable.

Il est possible d'implémenter quelque chose du genre :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    private static MethodInfo GetTaggedMethod(MethodInfo info)
{
MethodInfo ret = null;

do
{
var attr = info.GetCustomAttributes(typeof(MyAttribute), false) as MyAttribute[];

if (attr.Length != 0)
return info;

ret = info;

info = info.GetBaseDefinition();
}
while (ret != info);

return null;
}

Cette méthode à deux variables d'état et une boucle, ce qui la rend légèrement plus complexe à stabiliser. Cette méthode pourrait aisément être exprimée sous forme d'une requête LINQ, mais (pour autant que je sache) il n'existe pas de moyen pour créer une énumération à partir d'un élément qui fait partie d'une liste chaînée.

Pour être capable de faire cela, c'est à dire "Traverser" une liste d'objets du même type qui sont liés à leur suivant, une extension method contenant un iterator peut être écrite comme cela:

1
2
3
4
5
6
7
8
9
10
11
    public static class Extensions
{
public static IEnumerable<T> Traverse<T>(this T source, Func<T, T> next)
{
while (source != null)
{
yield return source;
source = next(source);
}
}
}

C'est un iterator très simple, qui se contente d'appeler une méthode pour connaître l'élément suivant à partir de l'élément courant, puis qui s'arrête lorsque la valeur suivante est nulle.

Cela peut être utilisé simplement comme ceci, en utilisant l'exemple de GetBaseDefinition :

1
2
3
   var methodInfo = typeof(Dummy).GetMethod("Foo");

IEnumerable<MethodInfo> methods = methodInfo.Traverse(m => m != m.GetBaseDefinition() ? m.GetBaseDefinition() : null);

Afin d'être précis, la Lambda  n'est pas parfaite, puisqu'elle appelle GetBaseDefinition deux fois. C'est certainement optimisable.

Quoi qu'il en soit, pour revenir au premier exemple, la méthode GetTaggedMethod peut être écrite en une seule requête LINQ, en utilisant l'extension Traverse:

1
2
3
4
5
6
7
8
9
    private static MethodInfo GetTaggedMethod(MethodInfo info)
{
var methods = from m in methodInfo.Traverse(m => m != m.GetBaseDefinition() ? m.GetBaseDefinition() : null)
let attributes = m.GetCustomAttributes(typeof(MyAttribute), false)
where attributes.Length != 0
select m;

return methods.FirstOrDefault();
}

C'est un avis très personnel, mais je trouve ce code plus lisible. C'est certainement une question de goût :)

Néanmoins, La liste chaînée composée de MethodInfo n'est pas l'exemple parfait, puisque la fin de la chaîne n'est pas une référence null, mais plutôt le même objet que l'on est en train de tester. La plupart du temps, la chaîne va se terminer avec un null; c'est pourquoi la méthode Traverse utilise null pour terminer l'énumération. J'ai utilisé cette méthode pour écrire des requêtes sur une hiérarchie d'objets du même type, pour lequel le parent de la racine est null. Cela s'est avéré être très utile et concis lorsqu'utilisée dans une requête LINQ.

Un détour par F#

Puisque j'en étais la, j'ai également essayé de déterminer ce que serait une version F# de ce code. Avec l'aide des fonctions récursives, j'ai écrit cela :

1
2
3
4
5
6
    let rec traverse(m, n) =
let next = n(m)
if next = null then
[m]
else
[m] @ traverse(next, n)

Une des parties intéressantes de F# est qu'il ne requiers pas de spécifier les types des valeurs. "m" est en fait un objet, et "n" est une fonction (obj -> obj), et retourne une liste d'objets. Et c'est utilisé comme ceci :

1
2
3
4
    let testMethod = typeof<Dummy>.GetMethod("Foo")

for m in traverse(testMethod, fun x -> if x = x.GetBaseDefinition() then null else x.GetBaseDefinition()) do
Printf.printfn "%s.%s" m.DeclaringType.Name m.Name

En fait, la version F# de la méthode traverse n'est pas exactement comme la version C#, puisque ce n'est pas une extension method et elle n'est pas évaluée de manière lazy. Elle est également plus verbeuse, principalement parce que je n'ai pas encore trouvé d'équivalent de l'opérateur ternaire "?:".

Après avoir creusé un peu dans la spécification de F#, j'ai trouvé qu'il existe un équivalent du mot clé C# yield, étonnamment nommé yield. Il s'utilise comme ceci :

1
2
3
4
5
6
7
8
9
    let rec traverse(m, n) =
seq {
let next = n(m)
if next = null then
yield m
else
yield m
yield! traverse(next, n)
}

Il s'utilise de la même manière qu'en C#, mais la valeur de retour n'est plus une liste mais une séquence.

Je trouve également intéressant que les fonctions en F# permettent de retourner des Tuples sans librairies ajoutées. Cela veut dire que pour ma recherche d'attribut, il est possible de retourner à la fois la méthode et l'attribut associé. Umbrella définit aussi ses Tuples, mais c'est une librairie additionnelle.

F# deviens de plus en plus intéressant, au fur et à mesure que je creuse dans ses fonctionnalités...

Hyper-V Virtual Machine Mover et Hyper-V Server

J'ai eu quelques mauvaises expériences récemment avec des disques dur qui se déteriorent, et qui avaient des VM Hyper-V dessus. Mon petit outil de déplacement de VM m'a permis de gagner un temps précieux pour remonter les VM concernées.

J'ai pris un peu de temps pour l'améliorer et cette fois ci il est possible d'attacher et détacher des VM situées sur des machines distantes et en particulier celles situées sur des Hyper-V Server.

Une page est disponible pour cet outil, le Hyper-V Virtual Machine Mover, version 1.0.1.0

Toujours pas de sources disponibles mais elles le seront sur CodePlex sous peu.

Un outil pour déplacer une Machine Virtuelle Hyper-V sans l'exporter

This post is available in english here.

Hyper-V est un magnifique outil, avec lequel la performance et la stabilité sont au rendez-vous. Mais coté administration, les outils laissent un peu à désirer, et malgré qu'une bonne partie des opérations courantes que l'on pourrait espérer avoir sont disponibles, elles n'en sont pas moins difficiles d'usage. On peut espérer que tout cela sera amélioré avec Windows Server 2008 R2.

Mais en attendant, les outils d'administration d'Hyper-V ne permettent que l'import d'une VM qui a au préalable déjà été exportée. Bien entendu, cela ne peut ne peut se faire que si la machine hôte originale est encore en fonctionnement. Dans le cas d'un serveur qui crash, exporter une VM deviens immédiatement plus complexe... voir impossible.

Il existe, ça et la, quelques techniques qui expliquent, à grand coup de mklink et icacls, comment recréer les liens symboliques et les permissions pour les fichiers de configuration de la VM. Mais cela reste particulièrement complexe et fastidieux, puisqu'il ne faut pas en oublier, et respecter un ordre bien précis, surtout si l'on veut effectuer l'opération d'attachement d'une VM sur un serveur live.

Après avoir trituré Hyper-V, ses symlinks et son interface WMI, j'ai donc créé un petit outil en mode GUI qui permet d'attacher et de détacher une VM qui n'a pas été exportée.

Quelques notes à son propos :
  • Une VM ne peut être détachée que si elle est en mode "Saved" ou "Stopped".
  • Il n'est pas nécessaire d'arrêter le service Hyper-V, toutes les modifications sont détectées en live par Hyper-V.
  • Une VM ne peux être importée que si elle comporte au moins un disque dur sur le controlleur IDE 0.
  • Tous les fichiers de la VM doivent être sous la même racine, HDD et Snapshots.
  • Tous les fichiers qui sont modifiés sont sauvegardés à coté des originaux, les fichiers de données ne sont pas modifiés, ni déplacés.
  • .NET 3.5 doit être installé.
Je mettrais à disposition les sources sous peu, ainsi qu'une version console.

Il y aura forcément des bugs, n'hésitez pas à m'en faire part. Je ne pourrais peut-être rien y faire, puisque il s'agit d'un outil qui effectue une opération qui (à priori) n'est pas prévue ou supportée par Microsoft.

L'outil est à télécharger ici.

Utiliser plusieurs clauses Where dans une requête LINQ

This article is available in English.

Pour faire suite à mon précédent article où j'avais besoin d'être capable d'intercepter les exceptions dans une requête, j'ai constaté qu'il est possible de spécifier plusieurs clauses Where dans une requête LINQ.

Voici la requête en question :

var q = from file in Directory.GetFiles(@"C:\Windows\Microsoft.NET\Framework\v2.0.50727", "*.dll")
        let asm = file.TryWith(f => Assembly.LoadFile(f))
        where asm != null
        let types = asm.TryWith(a => a.GetTypes(), (Exception e) => new Type[0])
        where types.Any()
        select new { asm, types };


Cette requête permet de trouver la liste des assemblies pour lesquelles il est possible d'en lister les types. L'intéret de placer plusieurs clause where est de ne pas avoir à évaluer inutilement des morceaux d'une query, si les morceaux précédents permettent de le savoir. Au passage, le TryWith autour du Assembly.GetTypes() est la pour intercepter des exceptions de chargement de types, au cas où des dependances ne seraient pas disponibles au moment de l'énumération.

C'est une petite astuce LINQ à savoir :)

F#, TryWith, Maybe et Umbrella

This post is available in English.

Je me suis lancé depuis quelques temps dans la découverte de F#, et bien que je n'ai pas l'intention d'en faire mon langage principal, j'ai bien l'intention de tenter d'utiliser les techniques et approches que l'on peut trouver dans ce langage et de les porter en C#. Les additions de C# 3.0 en font une bonne cible pour des concepts fonctionnels.

Il semble y avoir une sorte de consensus sur le fait que F# n'est pas un langage multi-usages, tout comme C# ne l'est pas, comme par exemple sur l'écriture de code parallèle. C# n'est pas adapté à cela, F# l'est bien plus. A l'inverse, F# ne semble pas tout à fait adapté à la création de GUI. Pour ce qui me concerne, et dans la mesure ou F# n'est pas vraiment officiel, la réutilisation des concepts suffit amplement pour le moment.

Extension TryWith

En F#, j'ai eu à écrire ceci :

    let AllValidAssemblies = [
        for file in Directory.GetFiles(@"C:\Windows\Microsoft.NET\Framework\v2.0.50727", "*.dll") ->
        System.Reflection.Assembly.LoadFile(file)
    ]

Ce code crée donc une liste des assemblies qui peuvent être chargées dans l'AppDomain courant. Il y a cependant un problème avec l'appel de la méthode Assembly.LoadFile, puisqu'elle lève une exception lorsque le fichier spécifié n'est pas chargeable. C'est un comportement qui n'est pas modifiable, alors qu'on voudrait par exemple retourner null à la place d'une exception.

Pour contourner cela, il existe une fonctionnalité de F# qui permet de faire cela :

    let EnumAllTypes = [
        for file in Directory.GetFiles(@"C:\Windows\Microsoft.NET\Framework\v2.0.50727", "*.dll") ->
        try System.Reflection.Assembly.LoadFile(file) with _ -> null
    ]

L'intéret du couple try/with ici, est de transformer n'importe quelle l'exception en référence nulle.

Pour transposer la création de cette liste en C# avec une query LINQ, le même problème se pose. Il faut pouvoir intercepter l'exception lancée par LoadFile et la transformer en référence nulle.

Voici la query équivalente en C#, sans gestion d'exception :

    var q = from file in Directory.GetFiles(@"C:\Windows\Microsoft.NET\Framework\v2.0.50727", "*.dll")
            let asm = Assembly.LoadFile(file)
            select asm;

Lorsque LoadFile lance une exception, l'exécution de la requête est interrompue, ce qui est un problème.

Les extension methods peuvent être d'une grande utilité à cet endroit, et même si une méthode normale pourrait faire l'affaire, on peut écrire ceci :

    public static class Extensions
    {
        public static TResult TryWith<TInstance, TResult>(this TInstance instance, Func<TInstance, TResult> action)
        {
           try {
              return action(instance);
           }
           catch {
              return default(TResult);
           }
        }
    }

L'idée de cette méthode étant de reproduire le comportement du bloc try/with de F#. Ayant donc cette méthode ajoutée, on peut modifier la requête LINQ comme ceci :

    var q = from file in Directory.GetFiles(@"C:\Windows\Microsoft.NET\Framework\v2.0.50727", "*.dll")
            let asm = file.TryWith(f => Assembly.LoadFile(f))
            select asm;

Ce qui revient à donner la même liste qu'en F#, avec des null pour les assemblies qui n'ont pas été chargées.

La méthode TryWith peut bien sur être surchargée pour être un peu plus flexible, comme par exemple appeler une méthode pour une exception donnée :

    public static TResult TryWith<TInstance, TResult, TException>(
           this TInstance instance, Func<TInstance, TResult> action,
           Func<TException, TResult> exceptionHandler
        )
        where TException : Exception
    {
        try {
           return action(instance);
        }
        catch (TException e) {
           return exceptionHandler(e);
        }
        catch {
           return default(TResult);
        }
    }

Il y a d'ailleurs un petit bug intéressant concernant ce code. Il se trouve que lorsque l'on exécute ceci :

    string value = null;
    var res = value.TryWith(s => s.ToString(), (Exception e) => "Exception");


Le comportement est différent lorsqu'il est exécuté avec ou sans debugger sur runtime .NET x86. Il semblerait que le générateur de code "oublie" d'ajouter le handler pour l'exception dont le type est TException, ce qui est un peu fâcheux. Ce n'est pas un gros bug, puisque cela ne se produit que lorsque le debugger x86 est présent. Avec le debugger pour le runtime x64, pas de problème. Pour ceux qui sont intéressé, le bug est sur Connect.

Extension Maybe

J'ai aussi ajouté récemment l'extension nommée Maybe dans Umbrella, qui finalement a un comportement assez similaire à TryWith, mais sans les exceptions :

    public static TResult Maybe<TResult, TInstance>(
         this TInstance instance,
         Func<TInstance, TResult> function)
    {
       return instance == null ? default(
TResult) : function(instance);
    }


L'intérêt de cette méthode est d'être capable d'exécuter du code si le membre d'origine est non-null. Par exemple :

    object instance = null;
    Console.WriteLine("{0}", instance.Maybe(o => o.GetType());


Cela permet de n'évaluer l'appel à GetType uniquement si "instance" n'est pas null. Dans un appel de méthode comme ceci, il est possible de l'écrire avec un bloc "if", mais dans une query LINQ, cela devient un peu plus complexe.

Ceci étant, l'idée derrière ce code n'est pas nouvelle et elle est similaire au concept fonctionnel des Monads. Ce sujet a été couvert à plusieurs reprises, et une implémentation plus proche de F# peut être trouvée sur le Blog de Matthew Podwysocki.

Pollution ?

Lorsque l'on parle de pollution liée aux Extension Methods, c'est parler de pollution d'Intellisense. On peut rapidement se retrouver avec un tas d'extensions qui ne sont pas contextuelles au code courant, ce qui peut rendre Intellisense inutilisable. Dans le cadre d'Umbrella donc, ces deux extensions posent le problème de la pollution de tous les types, puisqu'elles sont génériques et sans contraintes.

Certes, il ne s'agit que de deux extensions, qui de plus sont vraiment très génériques car elles peuvent s'appliquer à quasiment n'importe quel code, mais elles auraient éventuellement leur place dans un Extension Point d'Umbrella, plutôt que directement sur tous les types.

On pourrait par exemple avoir ceci :

    object instance = null;
    Console.WriteLine("{0}", instance.Control().Maybe(o => o.GetType());


J'ai cependant quelques problèmes avec cette approche : Pour arriver à faire un extension point, la méthode "Control" doit créer une instance de IExtensionPoint. Cela ajoute une instanciation de plus dans l'exécution, bien que la lambda crée elle même une instance de plus, on est donc plus à une instance près... Il y a aussi le fait que cela rallonge l'écriture, mais cela n'est que de l'esthétique. Affaire à suivre donc, ...

Dans tous les cas, il est intéressant de voir l'impact que l'apprentissage d'un nouveau langage influe sur le style et la manière d'écrire du code dans un langage que l'on utilise depuis longtemps...

VS2008 et le message "Unable to start Debugging for [your exe]"

Posté le samedi 22 novembre 2008 18:32 par jay :: 0 commentaire(s)
Classé sous
Depuis l'arrivée de VS2008 (avec ou sans SP1) et cela relativement aléatoirement, je tombe sur le message "Unable to start Debugging for [your_exe]" lorsque je tente de démarrer une session de debuggage. Un message plutôt générique, bien entendu sans aucune explication ni message d'erreur. Comme un message du genre "Error: An error has occured".

Après avoir recherché, et trouvé qu'un reboot ou un logout/login réglait le problème, j'ai finalement trouvé une solution beaucoup moins coûteuse en temps.

Il se trouve que ce bug se produit avec la séquence suivante :
  • Le debugging d'une solution Web avec Webdev.Webserver.exe, et VS2008 crash en cours de debug. (Terminer le process ne suffit pas)
  • Le redémarrage de VS2008, puis ouverture la même solution.
  • Il se peut qu'à ce moment il ne soit plus possible de debugger avec VS2008, avec le message décrit plus haut. Un "Attach to Process" ne fonctionnera pas mieux.
Pour pouvoir debugger à nouveau, il suffit de terminer tous les processus de l'exécutable "Webdev.Webserver.exe". Il semblerait qu'il y ait un lien assez fort entre le debugger et le mini serveur web, et que si le debugger crash, ce lien persiste et empeche tout autre debugging.

Un bug existe sur Connect pour cela.

Variables Locales et Expressions Lambda

This article is available in english.

Après une petite discussion avec Eric Lippert à propos d'un post sur l'utilisation dans une expression lambda d'une variable locale déclarée dans une boucle foreach, Eric m'a fait remarquer que le morceau de code suivant :

    int a = 0;
    Action action = () => Console.WriteLine(a);
    action();


N'est pas étendu par le compilateur vers ce code :

    [CompilerGenerated]
    private sealed class <>c__DisplayClass1
    {
       public int a;

       public void <Main>b__0()
       {
          Console.WriteLine(this.a);
       }
    }

    void Main()
    {

        int a = 0;

       var display = new <>c__DisplayClass1();
       display.a = a;

       var action = new Action(display.<Main>b__0);

       action();
    }

J'ai fait une supposition sur le fait qu'une variable locale était simplement copiée dans la "display class" lorsqu'elle n'est pas utilisée après la création de la lambda, ce qui n'est pas le cas.

Si l'on prend un exemple un peu différent, avec le code suivant :

    int a = 0;
    Action action = () => Console.WriteLine(a);
    a = 42;
    action();


Ma supposition aurait fait en sorte que l'exécution de ce dernier code aurait affiché 0. Or ce n'est pas correct car les expressions lambda (et les méthodes anonymes) "capturent" la variable et non la valeur; l'exécution doit donc afficher 42.

En fait, le dernier morceau de code est étendu de la manière suivante :

    var display = new <>c__DisplayClass1();

    display.a = 0;

    var action = new Action(display.<Main>b__0);

    display.a = 42;

    action();

On peut constater dans les faits que la variable qui était auparavant locale, donc sur la pile, a été "promue" en tant que champ membre de la "DisplayClass". Cela veut dire aussi que toutes les références faites à cette variable "locale", aussi bien à l'intérieur qu'à l'extérieur de la lambda, sont remplacée vers l'instance courante de "DisplayClass".

C'est finalement très simple, mais on sent que la "magie" derrière le sucre syntaxique de C#3.0 a été assez bien pensée !

Je finirais par un grand merci à Eric Lippert qui a pris le temps de me répondre, malgré qu'il soit très probablement surchargé par le développement de C# 4.0. (avec la contravariance des types génériques, yey !)

Expressions Lambda et boucles ForEach

This post is available in english here.

Pour pouvoir améliorer les performances d'un serialiseur de données, et afin d'utiliser une petite extension que j'ai ajouté récemment dans la librairie Umbrella, je suis tombé sur un petit "effet de bord" intéressant lors de l'utilisation de la création d'expressions lambda dans une boucle foreach.

Prenons cet exemple de code très simple :

    var actionList = new List<Func<int>>();

    foreach (var value in Enumerable.Range(0, 10))
    {
       actionList.Add(() => value);
    }

    actionList.ForEach(func => Console.Write("{0} ", func()));


Qui donne en résultat ceci :

   9 9 9 9 9 9 9 9 9 9

Ce n'est, bien entendu, pas vraiment ce à quoi on s'attendait.

Les expressions lambdas ont la particularité de permettre l'utilisation des variables accessibles au moment de leur déclaration. Cela rend ces expression très intéressantes, mais pour bien les utiliser, il faut comprendre comment ces expression sont matérialisées par le compilateur.

Comme beaucoup de fonctionnalités de C#, comme using, foreach, les iterateurs ou bien lock, les lambdas sont du sucre syntaxique destinés à simplifier l'écriture de code bien souvent verbeux. Il est tout à fait possible de faire une expansion du mot clé en code C#.

Prenons le cas de ce morceau de code :

    int a = 0;
    Action action = () => Console.WriteLine(a);
    action();


L'expression lambda est "matérialisée" par le compilateur C# sous forme d'une "DisplayClass", qui lui permet de stocker le contenu de la variable locale "a" :

    [CompilerGenerated]
    private sealed class <>c__DisplayClass1
    {
       public int a;

       public void <Main>b__0()
       {
          Console.WriteLine(this.a);
       }
    }


On peut constater que les identifiants pour la classe générée ne sont pas valides en C#, mais le sont tout à fait du point de vue du CLR. On constate aussi que la variable locale utilisée lors de la déclaration de la lambda se trouve en tant que variable membre de la classe contenant le code de l'expression lambda. Le compilateur écrit donc ceci pour créer l'expression lambda :

    int a = 0;

    var display = new <>c__DisplayClass1();
    display.a = a;

    Action action = new Action(display.<Main>b__0);

    action();


La encore, ce n'est pas du code valide en C#.

Mais alors, que se passe-t-il donc dans le cas de notre foreach, pour que la contenu de la variable soit répété ?

Si l'on analyse le premier exemple de code généré par le compilateur avec Reflector, on ne constate rien de particulier avec le visualiseur C# :

    List<Func<int>> actionList = new List<Func<int>>();
    using (IEnumerator<int> CS$5$0000 = Enumerable.Range(0, 10).GetEnumerator())
    {
       while (CS$5$0000.MoveNext())
       {
          int value = CS$5$0000.Current;
          actionList.Add(delegate {
             return value;
          });
       }
    }


L'expression lamda est représentée sous la forme d'une méthode anonyme, qui est effectivement un synonyme de lambda, mais cela n'explique toujours pas le comportement.

Il faut en fait regarder l'IL généré pour comprendre le comportement, et voici une version en C# du code généré :

    List<Func<int>> actionList = new List<Func<int>>();
    using (IEnumerator<int> CS$5$0000 = Enumerable.Range(0, 10).GetEnumerator())
    {
       var myLambda = new <>c__DisplayClass4();

       while (CS$5$0000.MoveNext())
       {
          int value = CS$5$0000.Current;

          myLambda.value = value;

          actionList.Add(new Func<int>(myLambda.b_0));
       }
    }


On constate assez facilement le problème ici : L'instance de la classe contenant la lambda n'est créée qu'une seule fois, et réutilisée plusieurs fois en réassignant une nouvelle valeur à chaque itération. Cela explique le fait que le résultat de l'exécution de toutes les expressions lambda retournent toujours la dernière valeur énumérée, puisque elles ont toutes la même instance du type "DisplayClass".

Par contre, si l'on écrit le code original sous cette forme :

    foreach (var value in Enumerable.Range(0, 10))
    {
       int myValue = value;
       actionList.Add(() => myValue);
    }


Le problème ne se produit plus, et cette fois ci, chaque lambda a bien la "bonne" valeur.

Du point de vue du compilateur, la création d'une nouvelle instance de classe "conteneur" de lambda doit certainement être la conséquence de la création d'une nouvelle variable. Dans le cas d'un foreach ce ne semble pas être le cas, et la variable est considérée comme n'étant créée qu'une seule fois, puis réutilisée.

Du point de vue du compilateur donc, une boucle foreach est étendue de la manière suivante :

    using (var it = Enumerable.Range(0, 10).GetEnumerator())
    {
       int value;

       while (it.MoveNext())
       {
          value = it.Current;
          actionList2.Add(() => value);
       }
    }


C'est peut-être une question d'interprétation, mais je ne m'attendais pas tout à fait à ça...

Il faut donc porter un minimum d'attention à la manière dont on utilise les variables locales dans les expression lambdas, en fonction de leur endroit de déclaration.

J'expliquerais dans un prochain article où Umbrella m'a été utile et pourquoi j'ai eu à utiliser les expression lambda dans une boucle foreach.

Travailler avec Umbrella et .NET 3.5

This article is available in english here.

Si vous utilisez .NET 3.5 et les nouvelles fonctionnalités fournies par C# 3.0, et tout spécialement LINQ, vous vous êtes certainement demandé pourquoi il n'y a pas d'extension nommée ForEach pour l'interface IEnumerable<T>.

A vrai dire, je n'ai toujours pas la réponse à cette question, bien qu'il semble que cela ai quelque chose à voir avec le fait que, par nature, une
Action<T> n'est la plupart du temps pas "pure". Ce la veut dire qu'elle modifie des variables quelque part. Je ne me rappelle pas ou j'ai trouvé cette explication, mais cela semble faire du sens d'un point de vue programmation fonctionnelle.

Mais vous vous demandez peut-être pourquoi alors, il y a une méthode ForEach sur List<T> ? La encore, je ne sais pas pourquoi il y persiste dans la BCL ce genre de problèmes de consistence, mais je sais qu'il existe une librairie qui essaye de corriger cela :

Umbrella
 
Celle librairie s'occupe de remplir les trous laissés par la BCL en ajoutant un ensemble de nouvelles Extension Methods qui facilitent l'utilisation des classes de .NET 3.5. Cette librairie n'est cependant pas un framework, principalement parce qu'il n'y a pas besoin de réarchitecturer son application pour pouvoir l'utiliser.

Il y a quelques semaines durant une présentation d'Umbrella au
.NET User Group de Montreal, les créateurs Francois Tanguay et Erik Renaud de nVentive, se sont fait poser la question "Par quoi on commence ?". C'est une question assez difficile à répondre, puisque Umbrella s'attache à beaucoup de types du framework.

Utiliser Umbrella

Voyons un exemple simple :

Enumerable
    .Range(20, 30)
    .ForEach(
       (index, value) => "Value #{0}={1}".InvariantCultureFormat(index, value)
    );

Comme vous pouvez le voir, utiliser Umbrella requiert très souvent l'emploi des lambda expressions. Cet exemple est une réponse à la question : "Comment avoir l'index d'une valeur énumerée dans un foreach ?" 

Une autre Extension Method fournie par Umbrella est la méthode Remove sur une instance de ICollection, en spécifiant un prédicat permettant de sélectionner les éléments à enlever :

var values = new List<int>(Enumerable.Range(1, 10));
values.Remove(v => v > 4);

Vous pouvez bien entendu écrire du code qui fait exactement cela, mais vous auriez à créer une liste temporaire pour stocker les éléments à enlever, puis les enlever de la collection.

Il y a aussi une extension de IDictionary<> qui simplifie l'utilisation de TryGetValue, pour laquelle il est nécessaire de spécifier un argument "out", ce qui est particulièrement agaçant à écrire. Umbrella fourni une méthode nommée GetValueOrDefault qui donne soit la valeur associée à la clé, soit default(TValue) si la clé n'est pas trouvée.

L'extension Action.ToDisposable()

Une extension particulièrement intéressant est la possibilité d'encapsuler un delegate de type Action dans une instance de IDisposable anonyme. Admettons que nous voulons profiler le temps d'exécution d'une méthode pour un scope donné. Il faudrait écrire quelque chose comme cela :

var w = Stopwatch.StartNew();
for (int i = 0; i < 1000; i++) { }
Console.WriteLine(w.Elapsed);


Il faudrait alors écrire le code qui entoure la boucle for pour toutes les sections à profiler. Mais il est aussi possible de tirer parti de la syntaxe "using" en écrivant une classe qui factorise ce code :

class ProfileScope : IDisposable
{
    Stopwatch w = Stopwatch.StartNew();
    void IDisposable.Dispose()
    {
       Console.WriteLine(w.Elapsed);
    }
}

Et cela s'utilise comme ceci :

using (new ProfileScope())
{
    for (int i = 0; i < 1000; i++)
    {
    }
}

Cette fois ci, profiler un bloc de code est bien plus simple a écrire, mais il existe un moyen plus simple d'écrire la classe utilitaire en faisant cela :

static IDisposable CreateProfileScope()
{
    var w = Stopwatch.StartNew();
    return Actions.Create(() => Console.WriteLine(w.Elapsed)).ToDisposable();
}

Il est donc possible d'utiliser l'extension nommée "ToDisposable" pour créer une instance de IDisposable qui appellera la lamda spécifiée.

Le but de la méthode est CreateProfileScope est d'éviter d'exposer les détails internes du code de profiling, simplement en exposant une instance de IDisposable.

Extension Points

L'utilisation des Extension Methods a cependant un désavantage : Elles sont tendance à "polluer" le type orignal. Par "Polluer", je veux dire qu'elles apparaissent dans la fenêtre d'IntelliSense. Donc, puisqu'il est particulièrement simple d'ajouter des Extension Methods, il est également très simple de se retrouver avec des dizaines de méthodes sur le type, ce qui rend l'IntelliSense bien moins utile.

Les développeurs d'Umbrella ont donc eu l'idée d'implémenter les Extension Points, qui sont un moyen de regrouper les Extension Methods de la même manière que les namespace regroupent les types.

D'un point de vue code, c'est une sorte de "magouille", mais d'un point de vue utilisation, c'est très ingénieux. Par exemple, faciliter l'utilisation de la sérialisation par des Extension Methods aurait à s'appliquer à tous les types qui existent et placer ces extensions directement sur System.Object ne serait pas une très bonne idée.

Les Extensions points s'utilisent comme ceci :

using (MemoryStream stream = new MemoryStream())
{
    int a = 0;
    a.Serialization().Binary(stream);
    // ...
}


Coté implementation Serialization est une Extension Method placée sur tous les types existants et qui retourne une instance de SerializationExtensionPoint, et "Binary" est une Extension Method qui étant le type ce type plutôt que System.Object.

Il est important de comprendre que Umbrella est composé pour la plupart de code particulièrement simple, et que vous avez probablement déjà partiellement développé dans votre propre projet. C'est le nombre important de ces méthodes utilitaires qui rend Umbrella intéressant.

Je vais écrire quelques autres posts à propos d'Umbrella; il y a bien d'autres extensions qui sont des "suppresseurs" de code particulierment intéressants :)

Les Arrêts de Bus de Montréal sur iPhone

This post is also available in English here.

J'ai mis à jour mon Site Web de recherche des arrêts de bus de Montréal pour qu'il fonctionne un peu mieux sur un iPhone (Thanks, Mr Sanx).

Maintenant, les prochains horaires de bus sont un peu plus lisibles, et les anciens horaires sont plus faciles à éliminer visuellement.

Il y a aussi d'autres fonctionnalités en préparation, que j'ajouterais prochainement :)

La géolocalisation sur iPhone serait un plus puisque malheureusement Google Gears n'est pas disponible sur iPhone. Il semblerait qu'il y ai une sorte d'extension nommée weblocate:// qui puisse faire cela pour une application web, mais çà semble assez fumeux...

Quoi qu'il en soit, vous pouvez visiter le site depuis Windows Mobile, un iPhone, ou depuis un ordinateur de bureau à cette adresse :

http://jaylee.org/stm

Voici un exemple de ce que le site présente :

Comment convertir un (gros) VMDK VMWare en VHD pour Hyper-V

This post is available in english here.

J'ai eu récemment à travailler avec Hyper-V, et je dois dire que c'est assez plaisant.

Les VMs sont très réactives, l'IO n'est plus le goulot d'étranglement que cela a pu être, l'impact des VMs sur la machine hôte est bien moindre qu'avec VPC ou VMWare, il supporte les snapshots et des ACL assez fines popur chaque VM. La vue sur la performance est bien entendu subjective, mais je trouve le tout plus rapide et bien mieux intégré à l'OS que les autres produits. Et à présent d'ailleurs, il existe une version gratuite de Windows Server 2008 appelée Hyper-V Server, qui permet de faire tourner une version minimale de windows en mode texte. Juste Hyper-V et ses VMs.

Au passage, je suis partiellement d'accord avec Paul Thurrott sur "l'expérience utilisateur" de Hyper-V qui n'est pas des plus élégantes dans le détail, imposant l'utilisation de scripting et d'informations trouvées sur des blogs. Mais c'est un produit pour les serveurs, et qui n'est définitivement pas fait pour une personne qui ne comprend pas un minimum ce qu'elle fait.

Récemment donc, j'ai tenté de convertir une VM VMWare vers Hyper-V, et le disque principal de 80Go de la VM avait été créé en mode "fixed flat", soit donc un seul gros fichier VMDK de 80Go.

Donc, j'ai essayé en premier le convertisseur de VMDK en VHD, qui malheureusement ne semble pas supporter les gros fichiers de 80Go. Je sais que cet outil fonctionne avec d'autres disques plus petits, ce doit donc être a cause de la taille du fichier.

J'ai essayé ensuite le VMWare Virtual Disk Manager pour convertir le gros fichier vers un jeu de mutiples fichiers VMDK de 2Go. Après la conversion, l'outil de conversion de VMDK vers VHD a très bien fonctionné, convertissant mes multiples VMDK en un seul VHD dynamique.

La conversion n'est cependant pas terminée, principalement à cause des drivers de stockage installés par windows pour une VM VMWare, qui ne sont pas compatibles avec Hyper-V. Cela veut dire un joli BSOD donnant INACCESSIBLE_BOOT_DEVICE, décrit ici et par le KB 314082.

Il existe deux moyens pour corrigers cela : Créer le fichier reg de l'article de la KB et l'intégrer directement dans la VM VMWare avant d'effectuer la conversion, ou bien de monter le VHD converti dans une autre VM Hyper-V, et de merger le fichier reg dans le SYSTEM hive de l'OS de destination.

La premiere possibilité était hors de question dans mon cas, déplacer à nouveau le disque aurait été une perte de temps.

Cela m'a donc laissé l'option de montage du registry hive. Voici comment s'y prendre :

  • Utiliser une autre VM sous windows pour monter le VHD, afin de pouvoir accéder au fichier hive SYSTEM.
  • Utiliser regedit pour charger le fichier hive SYSTEM situé dans le répertoire System32\config\system et le placer dans HKLM/temp
  • Modifier le ficher reg de la KB en replaçant toutes les références à "SYSTEM\CurrentControlSet" par "temp\ControlSet001", l'importer pour mettre à jour la configuration de démarrage par défaut,
  • Modifier une nouvelle fois le fichier reg pour remplacer toutes les références à "SYSTEM\CurrentControlSet" par "temp\ControlSet002", pour modifier cette fois ci la "Derniere bonne configuration", juste au cas où.
  • Décharger le fichier hive,
  • Arreter la VM courante,
  • Démarrer la nouvelle VM avec le VHD fixé et voilà !
L'opération relativement consommatrice en temps vu la taille des fichiers manipulés, mais qui finalement en vaut la chandelle. La VM fonctionne à présent proprement dans Hyper-V.

PocketIE et Assignation du SRC d'un Element IMG

This post is available in English here.

Avec le site web de Recherche des Arrêts de Bus de Montréal, afin d'utiliser Google Maps efficacement, il est impératif de laisser le browser client récupérer l'image par lui même. L'API de Google ne laisse pas une seule et même clé récupérer un grand nombre de cartes pour un même hôte, donc rediriger l'image depuis mon site n'est pas une option.

Je voulais donc avoir une URL générée en javascript pour pouvoir utiliser la largeur du corps HTML pour l'intégrer dans la requête vers Google Maps. J'ai donc créé un petit javascript qui construit une URL et l'assigne à l'attribut SRC de l'élément IMG qui va contenir la carte Google.

Et bien, cela semble simple mais pas tant que ça... L'élément IMG récupère bien l'image, mais sa taille n'est pas mise à jour avec celle de la nouvelle image. Dans mon cas, je place l'image dans un élément TABLE, la taille est donc celle par défaut pour une cellule.

Pour corriger cela, j'ai donc du assigner la hauteur et largeur de l'élément IMG, pour lui donner la bonne taille.

Pocket IE supporte minimalement le scripting, mais il est quelque peu évident que c'est un ajout complexe au moteur HTML... Allons Microsoft ! Vous pouvez faire mieux que ça ! Regardez ce qu'Opera a fait...

Mise à Jour du Moteur de Recherche des Arrêts de Bus de Montréal

This post is also available in english here.

Jusqu'à présent, les retours ont été bons pour mon petit utilitaire de recherche des arrêts de bus à Montréal, malgré que je ne fasse pas activement de publicité.

Pour utiliser de la terminologie Microsoft, je "dogfood" ma propre application, et j'ai trouvé quelque points à améliorer un peu :

  • A présent, si l'on utilise Google Gears, on n'est plus redirigé automatiquement vers la liste des arrêts de la position courante. Il est simplement affiché une petite carte Google de l'endroit trouvé, et un bouton qui permet d'utiliser cette position.
  • Quelques messages d'erreur sont affichés si des données incorrectes ou manquantes sont entrées. Cela pouvait être perturbant de ne pas avoir de message...
  • Toutes les cartes utilisent maintenant la totalité de la largeur de l'écran. Cela évite d'avoir des cartes mal redimensionnées sur un écran mobile. Je me demande d'ailleurs ce que cela va donner sur un iPhone....
  • J'ai corrigé légèrement l'affichage des distances en Km au lieu de metres lorsque cela est approprié, bien que si vous êtes dans une autre ville que Montréal, vous verrez probablement une trèèès grande distance s'afficher.
    Je me demande toujours si je dois filtrer les personnes qui sont hors de Montréal. Le but de ce site est aussi d'être une démo technologique de la Géo Localisation, donc même la distance est inutilisable, sa présence est malgré tout importante.
  • J'ai aussi ajouté quelques exemples de noms de rues pour aides les visiteurs qui ne sont pas de Montréal à voir ce que ce site peut faire. J'ai sélectionne au hasard une intersection et un numéro d'arrêt de bus qui peuvent être utilisés directement.
  • J'ai aussi fait une petite passe de validation XHTML, histoire d'être un peu compatible... :)

Maintenant, je pense que je vais essayer de m'assurer que la recherche Google va trouver le site en Anglais et Français. Pour l'instant, je vais avoir les mêmes problèmes que les pages du site pour logiciel Bluetooth Remote Control. Google parcours les pages des sites sans préciser de langue, et donc pour ce site, c'est l'anglais qui va sortir en premier puisque c'est le langage par défaut. J'ajouterais probablement un "/fr" et "/en" en base virtuelle pour chacun d'eux.

Comme toujours, si vous avez des suggestions ou rapports de bugs, je suis intéressé !

L'attribut InternalsVisibleTo en .NET 2.0 et les Assemblies Non Signées

Posté le mardi 2 septembre 2008 15:30 par jay :: 0 commentaire(s)
Classé sous

This post is available in english here.

Dans un récent projet, dans le but de faire du test unitaire, j'ai du utiliser l'attribut InternalsVisibleTo ,  qui étend la portée du qualificateur internal. Cela permet de séparer le code de test unitaire de l'assembly principale, et ainsi éviter de publier le code de test unitaire ou de publier les classes internes dans le simple but de pouvoir les tester.

C'est un attribut très intéressant à plusieurs titres, mais en l'utilisant, vous pourriez tomber face à ce message :

error CS1726: Friend assembly reference 'Dummy' is invalid. Strong-name signed assemblies must specify a public key in their InternalsVisibleTo declarations.

Le problème est que mes deux assemblies source et destination n'étaient pas signées. J'ai alors essayé d'ajouter un "faux" PublicKey ou PublicKeyToken comme suggéré ici et ici, mais comme beaucoup de monde, je ne veux pas m'encombrer avec la signature des assemblies pour le moment dans mon projet.

En fait, il se trouve que le compilateur considère l'assembly source comme "signée" si l'un des deux attributs AssemblyKeyFile ou AssemblyKeyName sont présents, même ils sont vides.

Donc, pour pouvoir utiliser l'attribut InternalsVisibleTo avec des assemblies non signées, enlevez simplement les attibuts AssemblyKeyFile et AssemblyKeyName si vous ne les utilisez pas.

Utiliser Google Gears Pour Trouver Les Arrêts de Bus de l'île de Montréal

This post is available in english here.

(Si vous ne voulez pas lire tout l'article, voici le site en question : http://jaylee.org/stm)

Cela fait un moment que je n'ai pas posté sur ce blog. Cette fois, je ne parlerais pas de Bluetooth, mais quand même de code .NET :)

J'ai été très occupé récemment, mais j'ai trouvé un peu de temps pour travailler sur un outil qui va m'aider beaucoup, et qui va aider beaucoup d'utilisateurs de Windows Mobile et d'utilisateur d'internet mobile en général (à Montréal).

Le réseau de Bus de Montréal est relativement grand, mais sa représentation dans le monde digital est plus que pauvre, et inexistante lorsque l'on parle d'internet mobile. Le site Web en question génère de très grosses pages qui ne sont pas adaptées à la navigation mobile.

La plupart du temps, il s'agit simplement d'avoir l'horaire du prochain bus, et c'est plutôt compliqué en utilisant les outils existants.

Il y a eu quelques initiatives récemment sur iPhone, et je voulais donner l'opportunité aux autre utilisateurs d'avoir les mêmes informations plus rapidement, avec un petit plus de Géo-Locatisation.

C'est la que Google Gears entre en action, où leur dernière mise à jour offre une API de Geo-Localisation qui donne une position approximative en utilisant les antennes GSM les plus proches. Malheureusement, cela ne fonctionne que sous Windows Mobile. Mais ne vous en faites pas, ce la fonctionne malgré tout ! Vous aurez simplement à taper un peu au clavier pour entrer l'intersection de rues la plus proche.

Après avoir récupéré la position, l'outil interroge une base de données (en utilisant Linq to SQL) pour récupérer l'arrêt de bus le plus proche, ainsi que le prochain horaire. J'interroge également Google Maps pour avoir une carte avec quelques marqueurs pour visualiser les arrêts. Cela peut-être utile, car la 'triangulation' par GSM est plutôt imprécise par nature.

Il est également possible de demander les horaires d'un arrêt de bus en particulier, en utilisant le numéro de l'arrêt précisé sur les panneaux. Le petit plus ici, par rapport au site original, est que les horaires passés de moins d'une demi heure aussi sont affichés, au cas où une rue en grande ligne droite permette de savoir si un bus est passé ou pas, et donc si il est simplement en retard.

Enfin, si vous êtes à Montréal et que vous avez une connexion internet mobile (ou standard), visitez le site à cette adresse : http://jaylee.org/stm

Tous commentaires ou suggestions sont les bienvenus !

ILogicalThreadAffinative, suite.

Posté le jeudi 15 mai 2008 04:09 par jay :: 0 commentaire(s)
Classé sous , ,

This post is also available in english here .

Dans un précédent post, je parlais d'une fonctionnalité du Framework .NET qui permet de passer des informations automatiquement d'une thread vers toute autre thread qu'elle crée.

En fait, le contexte d'appel passe non seulement aux Threads du Thread Pool, mais également à toute instance de System.Threading.Timer et aux IO Completion Threads, comme celle utilisées avec les sockets.

J'ai été surpris par le fait que dans les premiers étages du pipeline remoting coté serveur, il y avait des informations dans le CallContext et ce bien avant que le message entrant n'ai été interprété. Mais cela n'était pas tout à fait ce à quoi je m'attendais; c'était des données qui étaient présentes dans le contexte d'appel lors de l'appel à RemotingConfiguration.Configure().

Cela prend du sens, puisque tous les sockets utilisés par le TcpChannel et HttpChannel sont créés lors de l'appel à cette méthode, et des opérations asynchrones sont démarrées à ce moment la. Donc, pour éviter d'avoir les données qui transitent de l'autre coté, il y a ces deux méthodes sur la classe ExecutionContext pour supprimer et restaurer le passage du context d'appel.

IEnumerable<T>.Any() vs. IEnumerable<T>.Count() != 0

Posté le dimanche 11 mai 2008 00:15 par jay :: 2 commentaire(s)
Classé sous , ,

An english version is available here .

Après avoir lu ce post d'Eric Lippert, je me suis rappelé que dans l'exemple de ce post, j'utilise IEnumerable<T>.Count() ou je ne me sers pas vraiment la valeur de retour, et du coup, mon code énumère la totalité de la collection inutilement.

J'aurais pu utiliser IEnumerable<T>.Any(), qui après avoir regardé le code IL, démarre simplement l'énumération et s'arrête juste après un élément puis retourne true, ou si il n'y a rien, retourne false.

Bien plus efficace, à défaut d'utiliser PLinq !

Aventures avec le mot clé "let" dans LINQ to Objects

Posté le samedi 10 mai 2008 17:35 par jay :: 3 commentaire(s)
Classé sous , ,

This post is also available in english here .

Je me suis finalement décidé à blogger dans les deux langues, français et anglais. BlogEngine.NET ne me permet pas d'écrire mes posts dans les deux langues, et après avoir fait quelques essais de site multilingue sur la même url, il se trouve que Google n'indexe pas vraiment très bien ce genre de contenu dynamique.

Quoi qu'il en soit, j'ai eu le temps dernièrement de jouer avec LINQ to objects, et en particulier avec le mot clé "let".

J'avais un grand nombre de fichiers XML placés dans des répertoires multiples, et je voulais les filtrer avec une expression régulière. D'abord, voici comment j'ai récupéré tous les fichiers dans mes répertoires :

  var files = from dir in Directory.GetDirectories(rootPath, SearchOption.AllDirectories)
              from file in Directory.GetFiles("", "*.*")
              select new { Path = dir, File = Path.GetFileName(file) };

Arrivé à ce point, j'aurais pu éviter d'utiliser Directory.GetDirectories puisque GetFiles permet également de chercher recursivement. Mais puisque GetFiles ne retourne qu'un seul tableau de String et non un énumérateur, cela aurait voulu dire que tous mes fichiers auraient été retournées en un seul tableau, ce qui n'est pas très efficace en terme de consommation mémoire. J'aurais préféré avoir une implémentation de GetDirectories et GetFiles basée sur un itérateur, mais la plus fine énumération ne peut être faite que comme ca, sans appel vers du code natif.

Ensuite, ayant tous mes fichiers, je voulais à présent les filtrer en utilisant une expression régulière, puisque les fichiers que je voulais analyser suivaient un format de nom spécifique. J'ai donc mis à jour ma requête comme suit :

    Regex match = new Regex(@"(?<value>\d{4}).xml");
    var files2 = from dir in Directory.GetDirectories(args[0], "*", SearchOption.AllDirectories)
                 from file in Directory.GetFiles(dir, "*.xml")
                 let r = match.Match(Path.GetFileName(file))
                 where r.Success
                 select new {
                    Path = dir,
                    File = Path.GetFileName(file),
                    Value = r.Groups["value"].Value
                 };

Cette fois, j'ai introduit le mot clé let. Ce mot clé est intéressant puisqu'il permet la création d'une variable locale à la requête qui peut contenir à la fois des collections ou des objets simples. Le contenu de cette variable peut être réutilisé dans la clause where, comme source d'une autre requête from, ou dans le select.

Dans mon cas, je voulais simplement avoir le résultat de l'évaluation de mon expression régulière, donc j'ai simplement appelé Regex.Match pour valider le nom du fichier. Enfin, je place le contenu d'un groupe de l'expression dans le type anonyme résultant.

Avec tous mes fichiers filtrés, j'ai constaté ensuite que certains n'étaient pas valides puisqu'il ne contenaient pas un noeud XML spécifique. J'ai adapté ma requête comme cela :

    var files2 = from dir in Directory.GetDirectories(args[0], "*", SearchOption.AllDirectories)
                 from file in Directory.GetFiles(dir, "*.xml")
                 let r = match.Match(Path.GetFileName(file))
                 let c = XElement.Load(file).XPathSelectElements("
//dummy")
                 where r.Success && c.Count() != 0
                 select new {
                    Path = dir,
                    File = Path.GetFileName(file),
                    Value = r.Groups["value"].Value
                 };

J'ai ajouté un nouveau let pour me permettre de charger le document XML, et m'assurer que le noeud nommé "dummy" existe quelque part dans le codument. Au passage, si vous cherchez XPath dans XLinq, regardez par ici.

Vous vous demandez peut-être quand le XElement.Load est effectivement évalué... et bien il n'est sulement évalué lors de l'appel du c.Count(), qui ne l'est que lorsque la regex a validé le fichier concerné. De cette manière, je ne suis pas en train d'essayer de charger tous les fichiers retournés par GetFiles. Il faut se rappeler que les requêtes LINQ ne sont évaluées que lorsqu'elles sont énumérées.

En conclusion, LINQ est une technologie particulierement intéressante, et définitivement pas seulement réservée pour l'interrogation de bases de données. Ce que j'apprécie le plus et qu'il est possible d'écrire du code pratiquement sans boucles, ce qui permet de réduire les effets de bord.

Si vous n'avez pas jeté un coup d'oeuil sur LINQ, essayez, vous aprécierez probablement !

.NET Threads, CallContext et ILogicalThreadAffinative

This post is also available in english here .

J'ai récemment cherché un moyen de passer automatiquement des informations du contexte d'appel d'une thread vers tout autre thread créée à partir de la première. Cela peut-être utile pour de multiples raison, comme par exemple avoir des informations contextuelles placées dans le TLS à propos de l'utilisateur courant, ou pour tout autre information contextuelle spécifique à l'application.

Il est possible d'obtenir ce comportement en entourant le point d'entrée d'une thread de code qui va passer les informations à la nouvelle thread. C'est généralement assez fastidieux, voire complexe lorsqu'il s'agit de passer des informations générées en interne par un framework, puisqu'il faut absolument intercepter toutes les créations de thread.

Il existe un moyen pour faire cela en utilisant la class CallContext, et en particulier les méthodes GetData/SetData. Le problème est que si l'on place des données dans le CallContext, elles ne seront pas passées à une nouvelle thread. En pratique, pour que les données soient passées, il suffit que l'objet placé dans le CallContext implémente l'interface ILogicalThreadAffinative.

C'est une sorte d'interface marqueur qui permet d'éviter de faire transiter des données entre Threads lorsqu'elle n'y sont pas destinées.

Il est également intéressant de noter que les types marqués avec ILogicalThreadAffinative seront passés aux threads créées par le Thread Pool, et donc aux delegates placés dans la queue par l'appel à la méthode BeginInvoke qui est générée par le compilateur C#.

Enfin, dans le cas d'un appel distant en Remoting, tout type marqué avec ILogicalThreadAffinative sera sérialisé vers le contexte d'appel distant, puis sérialisé à nouveau en retour vers le contexte local.

Etre capable de faire du pas-à-pas dans le code du Framework a été d'une grande aide pour comprendre tout cela ;)

Plus de Messages Page suivante »

Les 10 derniers blogs postés

- [Refactoring] ReSharper pour Visual Studio 2010 (Preview) par Thomas Jaskula le il y a 10 heures et 43 minutes

- [Refactoring] Analyser vos exceptions avec ReSharper Exceptional par Thomas Jaskula le il y a 11 heures et 57 minutes

- SharePoint 2007 : patterns & practices SharePoint Guidance par Philippe Sentenac [MVP SharePoint] le 07-03-2009, 09:56

- [Visual Studio 2010] Les tests cases c’est bien, mais je vais devoir tout réécrire ? par Etienne Margraff le 07-03-2009, 09:00

- MVP[Gribouillon].AddYear par The Grib's Lair [Sébastien PICAMELOT - MVP SharePoint] le 07-03-2009, 08:45

- Clinique INSIA - Projet de fin d’Etudes (Silverlight 3 MVVM et OutOfBrowser, WCF, TFS) - Part 1 par David REI le 07-02-2009, 23:38

- C’est la crise ? Bah pourquoi cramer du budget pub alors ? par Nix's Blog le 07-02-2009, 15:31

- Soyons MVP ! par TheSaib .NET blog le 07-02-2009, 12:15

- SharePoint : Gestion des Erreurs 6398, 7076 et 6482 par Blog Technique de Romelard Fabrice le 07-02-2009, 11:53

- EF avec WPF par Matthieu MEZIL le 07-02-2009, 10:18