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 :
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 :)