Dot.Blog

C#, XAML, Xamarin, UWP/Android/iOS

C# - méthodes d’extension utiles

[new:30/03/2014]Les méthodes d'extension sont aujourd'hui bien connues même si elles restent peu utilisées. Pourtant elles permettent de créer des bibliothèques de services facilement portables d'un projet à un autre et pouvant s'enrichir avec le temps. Aujourd'hui j'avais envie de revenir sur cette feature de C# et de vous en parler un peu !

Extensibilité

Tout le monde parle de « scalability » de « modularité », d'extensibilité. Tous ces termes font références à la capacité qu'un système a de pouvoir grossir en s'adaptant au besoin. Qu'il s'agisse de la « scability » propre aux serveurs, de la modularité telle qu'on peut la même en œuvre avec MEF ou d'extensibilité au sein même du code il existe mille raisons de vouloir se préparer dès le départ au changement. Car dans notre métier tout change, tout le temps. Ceux qui aiment les environnements statiques restant égaux à eux-mêmes durant toute une carrière feront des informaticiens malheureux, et souvent bien mauvais…

Il faut de la légèreté, passer du coq à l'âne, parfois tout refaire juste parce qu'il faut le refaire « autrement », pas forcément mieux mais « autrement » - même si ce n'est pas la meilleure raison qu'on puisse invoquer d'ailleurs.

Gérer ce changement permanent fait donc parti de notre métier. Et nous devons faire en sorte que tout soit toujours conçu avec le changement en tête.

Une application est aujourd'hui généralement faite de classes offrant méthodes et propriétés, des classes interagissant avec d'autres classes. Parmi celles-ci se trouvent des classes de services, des classes utilitaires chargées de fournir une aide au code lui-même pour le rendre moins répétitif, plus concis.

C'est une façon de prévoir le changement, travailler par services, utiliser des Interfaces, des Factory, tirer profit de MEF, etc.

Dans le code lui-même il est possible aussi de prévoir le changement, en tout cas l'amélioration au fil du temps, la simplification et le respect du célèbre DRY (Don't Repeat Yourself – Ne Vous Réptétez Pas !).

Les extensions de code peuvent jouer un rôle important dans un tel cadre en permettant d'enrichir de nouveaux comportements des objets de toute nature, le tout sans toucher au code ces objets et en ayant la possibilité de regrouper en un seul point toutes les extensions pour en faciliter la maintenance.

Donc bien se servir des méthodes d'extensions C# c'est bien se servir du langage, tout simplement.

Du concret

Rester dans l'intention, dans l'académique et les grandes explications sur un tel sujet serait vraiment trop triste car rien ne vaut quelques exemples pour se convaincre de l'utilité du procédé. Plus loin cela nous permettra d'aborder quelques astuces qui peuvent rendre service dans bien des cas.

Enum et Description

On n'y pense pas toujours mais en dehors des américains qui peuvent se permettre d'utiliser souvent les mêmes mots courts pour désigner un élément d'énumération et son affichage en clair l'informaticien français par exemple est lui souvent obligé de jongler entre les valeurs de l'énumération et leur traduction en langage humain compréhensible par un utilisateur (listes de choix dans des combobox, des listes…).

Or il existe bien un moyen d'indiquer pour chaque élément d'une énumération une « description » via un attribut, valeur qui pourra ensuite être récupérée pour afficher des choses plus présentables.

Ce besoin pouvant se faire sentir souvent dans une application et même dans plusieurs de celles-ci, pourquoi ne pas créer une extension qui règlera la question définitivement ?

Regardons cet extrait de code :

void Main()

{

string description = EnumHelper<EnumGrades>.GetEnumDescription("pass");

Console.WriteLine(description);

}

 

public enum EnumGrades

{

[Description("Passed")]

Pass,

[Description("Failed")]

Failed,

[Description("Promoted")]

Promoted

}

 

 

public static class EnumHelper<T>

{

public static string GetEnumDescription(string value)

{

Type type = typeof(T);

var name = Enum.GetNames(type).
Where(f => 
f.Equals(value, StringComparison.CurrentCultureIgnoreCase)).
Select(d => d).FirstOrDefault();

 

if (name == null)

{

return string.Empty;

}

var field = type.GetField(name);

var customAttribute = 
field.GetCustomAttributes(typeof(DescriptionAttribute), false);

return customAttribute.Length > 0 
? ((DescriptionAttribute)customAttribute[0]).Description 
: name;

}

}

 

Ce code a été testé et copié depuis LINQPad, ce merveilleux utilitaire qui évite de sortir VS à chaque fois qu'on a besoin de tester un petit bout de code…

L'attribut « Description » se trouve dans System.ComponentModel qu'il faut ajouter.

L'extension cherche sur la base d'un nom d'élément d'énumération à retrouver l'item lui-même puis sa description si elle existe. Selon que cela possible ou non c'est soit l'item soit la fameuse description qui est retournée.

Désormais placée dans une bibliothèque de code facilement transportable d'un projet à l'autre, cette extension permet de régler le problème des descriptions en langage clair des énumérations. Une fois pour toutes (si on n'est pas trop exigeant dans les traductions bien entendu car la localisation est un sujet cauchemardesque dès lors qu'on tente de le régler « jusqu'au bout » …).

Cryptographie

Encore un besoin courant : celui de crypter et décrypter un bout de texte, un mot de passe, etc. Posséder les extensions déjà écrites et rangées dans une bibliothèque d'utilitaires simplifie énormément la vie et force à utiliser des méthodes éprouvées.

Voici le code exemple copié depuis LINQPad :

void Main()

{

 string secret = "Mon Secret";

string encoded = secret.Encrypt("mykey");

Console.WriteLine("Encodé: "+encoded);

string decoded = encoded.Decrypt("mykey");

Console.WriteLine("décodé: "+decoded);

 

}

 

public static class Extensions

{

 

public static string Encrypt(this string stringToEncrypt, string key)

{

if (string.IsNullOrEmpty(stringToEncrypt))

{

throw new ArgumentException("An empty string value cannot be encrypted.");

}

 

if (string.IsNullOrEmpty(key))

{

throw new ArgumentException("Cannot encrypt using an empty key. Please supply an encryption key.");

}

 

CspParameters cspp = new CspParameters();

cspp.KeyContainerName = key;

 

RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(cspp);

rsa.PersistKeyInCsp = true;

 

byte[] bytes = rsa.Encrypt(System.Text.UTF8Encoding.UTF8.GetBytes(stringToEncrypt), true);

 

return BitConverter.ToString(bytes);

}

 

 

public static string Decrypt(this string stringToDecrypt, string key)

{

string result = null;

 

if (string.IsNullOrEmpty(stringToDecrypt) ||
string.IsNullOrEmpty(key) )

{

throw new ArgumentException("Empty input or key are not allowed.");

}

 

CspParameters cspp = new CspParameters();

cspp.KeyContainerName = key;

 

RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(cspp);

rsa.PersistKeyInCsp = true;

 

string[] decryptArray = stringToDecrypt.Split(new string[] { "-" }, 
StringSplitOptions.None);

byte[] decryptByteArray = Array.ConvertAll<string, byte>(decryptArray, 
(s => Convert.ToByte(byte.Parse(
s, System.Globalization.NumberStyles.HexNumber))));

 

byte[] bytes = rsa.Decrypt(decryptByteArray, true);

return System.Text.UTF8Encoding.UTF8.GetString(bytes);

} }

 

Et voici un exemple de sortie :

Encodé: 14-12-93-FB-F2-0D-34-8F-12-84-0F-6C-7E-A0-2F-EE-C5-1D-28-70-DF-2E-DD-96-2D-4C-B8-DA-1E-00-72-79-1C-52-03-9D-F7-F0-DD-E9-09-28-64-A0-CE-EB-DA-C1-4A-C7-AC-2F-83-54-30-79-03-08-7C-99-52-04-79-A4-52-6F-66-28-6E-6F-32-BA-AD-DB-87-44-25-46-DA-68-10-5C-FE-E3-84-46-E2-EC-A8-EA-C2-2C-F1-40-EF-57-62-C0-9B-4B-CA-FD-CC-BA-FC-A9-51-C1-96-BA-AD-85-4C-82-09-58-52-66-A1-E2-93-C9-02-D9-5A-56-C9-73
décodé: Mon Secret

Il est bien entendu nécessaire d'ajouter un using de System.Security.Cryptography pour utiliser les fonctions RSA.

Une fois encore un problème récurrent se trouve réglé définitivement par une méthode d'extension. Toute chaîne de caractères se voit dès lors la possibilité d'être codée ou décodée comme si cela avait toujours fait partie des méthodes de la classe String…

De outils simples

Les méthodes d'extension ne doivent pas forcément être l'occasion de planquer 15 pages de codes derrière un petit nom… Ni en aucun cas de la logique métier – sauf si une telle stratégie est clairement adoptée, justifiée et documentée. Rien n'est plus affreux à comprendre et à maintenir qu'un code utilisant de travers les méthodes d'extension…

En revanche coder des petites choses dont on se sert souvent et qu'on réécrit tout aussi souvent, c'est tirer profit au maximum du procédé.

Prenons l'exemple d'une comparaison entre deux bornes. Ecrire une fois pour toute une méthode d'extension générique est malgré tout plus subtile que d'écrire des tas de fois des comparaisons de bornes une fois pour une date, une fois pour chaîne, une autre fois pour un entier, etc…

Il est bien plus simple d'écrire :

public static bool Between<T>(this T value, T from, T to) where T : IComparable<T>

{

return value.CompareTo(from) >= 0 && value.CompareTo(to) <= 0;

}

Et de l'utiliser partout ainsi :

var debut = 500;

var fin = 1000;

var isOk = 1500.Between(debut,fin);

ou

isOk = "Test".Between("Alfred","Sophie");

Etc.

 

On peut aussi penser à une séquence qui met une liste en désordre. Souvent on utilise des listes triées, mais parfois on les veut dans un ordre imprévisible. Prenons n'importe quel IEnumerable et secouons-le un peu, cela donne la méthode d'extension :

public static class Tools

{

private static readonly Random rand = new Random();

 

public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> list)

{

return list.Select(x => new { Number = rand.Next(), Item = x }).
OrderBy(x => x.Number).
Select(x => x.Item).ToList();

}

}

On remarquera que la classe statique d'accueil de la méthode déclare elle-même l'objet de randomization, tout simplement parce qu'on suppose que cette classe « Tools » en fera peut-être usage ailleurs et que l'aléatoire est bien meilleur avec une seule variable de ce type qu'en en créant une instance à chaque appel de méthode…

Conclusion

Il est bon parfois de revenir sur des éléments précis de C#, tout le monde connait, mais bizarrement quand j'audite du code rien n'est vraiment bien utilisé… Alors une petite couche sur les méthodes d'extension ça ne peut pas faire de mal !

Et si vous désirez trouver des méthodes toutes écrites pour tous les usages (méfiez-vous des bogues tout de même il y en a pas mal), regardez ce site qui regorge d'exemples : http://www.extensionmethod.net/

Stay Tuned !

 

 

blog comments powered by Disqus