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

- Merci par Blog de Jérémy Jeanson le 10-01-2019, 20:47

- Office 365: Script PowerShell pour auditer l’usage des Office Groups de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 11:02

- Office 365: Script PowerShell pour auditer l’usage de Microsoft Teams de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 10:39

- Office 365: Script PowerShell pour auditer l’usage de OneDrive for Business de votre tenant par Blog Technique de Romelard Fabrice le 04-25-2019, 15:13

- Office 365: Script PowerShell pour auditer l’usage de SharePoint Online de votre tenant par Blog Technique de Romelard Fabrice le 02-27-2019, 13:39

- Office 365: Script PowerShell pour auditer l’usage d’Exchange Online de votre tenant par Blog Technique de Romelard Fabrice le 02-25-2019, 15:07

- Office 365: Script PowerShell pour auditer le contenu de son Office 365 Stream Portal par Blog Technique de Romelard Fabrice le 02-21-2019, 17:56

- Office 365: Script PowerShell pour auditer le contenu de son Office 365 Video Portal par Blog Technique de Romelard Fabrice le 02-18-2019, 18:56

- Office 365: Script PowerShell pour extraire les Audit Log basés sur des filtres fournis par Blog Technique de Romelard Fabrice le 01-28-2019, 16:13

- SharePoint Online: Script PowerShell pour désactiver l’Option IRM des sites SPO non autorisés par Blog Technique de Romelard Fabrice le 12-14-2018, 13:01