Aujourd'hui, petit billet pour vous présenter une méthode originale pour accéder à une propriété d'un objet via des delegate sur les accesseurs.
Une propriété, qu'est-ce que c'est ?
Pour ce point, je me permets de citer la FAQ C# : qu'est-ce qu'une propriété ?
Une propriété est un membre d'une classe (ou structure, ou interface) qui s'utilise comme un champ, mais peut implémenter sa propre logique pour accéder à la valeur. Une propriété se compose généralement de 2 accesseurs, get (pour récupérer la valeur) et set (pour la modifier).
Une propriété peut être en lecture/écriture (accesseurs get et set), en lecture seule (seulement un accesseur get), ou, plus rarement, en écriture seule (seulement un accesseur set).
Une propriété peut être en lecture/écriture (accesseurs get et set), en lecture seule (seulement un accesseur get), ou, plus rarement, en écriture seule (seulement un accesseur set).
- GetMaPropriete : qui est utilisée lorsqu'un accès en lecture est réalisé sur la propriété ;
- SetMaPropriete : qui est utilisée lorsqu'un accès en écriture est réalisé sur la propriété.
Accesseurs et delegate
Mais une limitation du langage C# est qu'il n'est pas possible d'accéder à ces méthodes, et donc notamment il est impossible de les utiliser pour initialiser des delegates.
Une méthode, sans doute la plus simple, est d'utiliser la réflexion pour récupérer de tels delegates. Plusieurs soucis se pose malgré tout :
- l'usage de la réflexion passe par l'utilisation de chaînes de caractères pour spécifier le nom des propriétés auxquelles on souhaite accéder. Aussi, en cas de changement de nom (par exemple dans le cadre d'un refactoring), il faudra changer manuellement le nom de ces propriétés ;
- il est possible de limiter la casse en utilisant l'opérateur nameof pour obtenir directement une chaîne de caractère à partir d'une propriété. Le refactoring refonctionne alors mais il incombe au développeur de l'utiliser à chaque fois. Et un oubli est si vite arrivé...
Une idée...
Ce qui serait bien, ce serait de pouvoir écrire quelque chose comme ceci :
Code C# : | Sélectionner tout |
1 2 | Action<int> setter = myObject.MyProperty.GetSetter(); Func<int> getter = myObject.MyProperty.GetGetter(); |
Malheureusement, ce n'est pas possible. On peut toutefois avoir quelque chose d'approchant :
Code C# : | Sélectionner tout |
1 2 | Action<int> setter = PropertyAccessor.Setter(program, x => x.MyProperty); Func<int> getter = PropertyAccessor.Getter(program, x => x.MyProperty); |
On a donc une méthode statique qui prend deux paramètres :
- un objet, dont on veut accéder une propriété ;
- une méthode anonyme qui fait référence à la propriété pour laquelle on souhaite un accesseur.
Ici, la méthode anonyme n'est pas utilisée en tant que telle. Si la curiosité vous pousse à regarder le code ci-dessous, vous constaterez que la méthode anonyme n'est jamais appelée. A la place, elle est examinée afin d'y extraire les informations sur la propriété que l'on souhaite rendre accessible grâce à la réflexion.
L'avantage majeure par rapport à la méthode précédente, est que cette fois-ci, ce n'est pas une chaîne de caractères qui nous permet de connaître la propriété à rendre accessible, mais une méthode anonyme. Le typage est donc plus fort mais également plus logique que l'usage de l'opérateur nameof.
Voici un petit programme d'exemple, qui fournit l'implémentation de la classe PropertyAccessor ainsi qu'un exemple d'utilisation :
Code C# : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace PropertyAndDelegate { public class Program { public static class PropertyAccessor { public static PropertyInfo GetProperty<T, TValue>(Expression<Func<T, TValue>> expr) { Expression body = null; if (expr != null && expr.Body != null) { body = expr.Body; } if (body != null && body.NodeType == ExpressionType.MemberAccess) { MemberExpression member = body as MemberExpression; return member.Member as PropertyInfo; } else { throw new ArgumentException("expr"); } } public static Action<TValue> Setter<T, TValue>(T o, Expression<Func<T, TValue>> expr) { PropertyInfo property = GetProperty(expr); return x => property.SetValue(o, x); } public static Func<TValue> Getter<T, TValue>(T o, Expression<Func<T, TValue>> expr) { PropertyInfo property = GetProperty(expr); return () => { return (TValue)(property.GetValue(o)); }; } } static void Main(string[] args) { Program program = new Program(); // Ecriture classique Action<int> setter1 = PropertyAccessor.Setter<Program, int>(program, x => x.MyProperty); Func<int> getter1 = PropertyAccessor.Getter<Program, int>(program, x => x.MyProperty); // Ecriture simplifiée. Les types sont automatiquement déduit des paramètres Action<int> setter2 = PropertyAccessor.Setter(program, x => x.MyProperty); Func<int> getter2 = PropertyAccessor.Getter(program, x => x.MyProperty); // setter1 et setter2 peuvent être utilisées pour modifier la valeur de la propriété MyProperty de l'instance program ! setter1(5); // Equivalent à program.MyProperty = 5 Console.WriteLine(program.MyProperty); // getter1 et getter2 peuvent être utilisées pour accéder aux valeurs la propriété MyProperty de l'instance program. Console.WriteLine(getter2()); Console.ReadLine(); } public int MyProperty { get; set; } } } |
Pour terminer, voici la discussion m'ayant donné l'idée pour écrire ce billet, avec un cas d'application concret.