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

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...

Publié lundi 18 mai 2009 22:14 par jay
Classé sous , , ,
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :

Commentaires


Les 10 derniers blogs postés

- TechDays Paris 2010 : Déploiement de nouvelles technologies – Retour d’expérience par l’informatique de Microsoft par Blog Technique de Romelard Fabrice le il y a 1 heure et 7 minutes

- TechDays Paris 2010 : Plan de migration vers SharePoint 2010 par Blog Technique de Romelard Fabrice le il y a 4 heures et 50 minutes

- TechDays Paris 2010 : La pleinière du second jour par Blog Technique de Romelard Fabrice le il y a 5 heures et 55 minutes

- Visual Studio 2010 and .NET Framework 4 Release Candidate now available par Matthieu MEZIL le il y a 9 heures et 1 minutes

- Création d’une base de donnée sous SQL Azure par Le Blog (Vert) d'Arnaud JUND le il y a 9 heures et 57 minutes

- TechDays Paris 2010 : Les Services d’applications dans SharePoint 2010 par Blog Technique de Romelard Fabrice le il y a 19 heures et 57 minutes

- TechDays Paris 2010 : La GED et SharePoint 2010 par Blog Technique de Romelard Fabrice le il y a 23 heures et 55 minutes

- TechDays Paris 2010 : SharePoint 2010 et Les réseaux sociaux par Blog Technique de Romelard Fabrice le 02-08-2010, 15:40

- TechDays Paris 2010 : SharePoint 2010 – Description et nouveautés par Blog Technique de Romelard Fabrice le 02-08-2010, 14:33

- TechDays Paris 2010 : Pleinière Lundi par Blog Technique de Romelard Fabrice le 02-08-2010, 14:30