IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Voir le flux RSS

François DORIN

[Actualité] Delegate sur des propriétés en C#

Note : 5 votes pour une moyenne de 4,20.
par , 27/12/2016 à 20h41 (3077 Affichages)
Bonjour,

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).
Autrement dit, derrière une propriété MaPropriete, se cache une méthode :
  • 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 - Visualiser dans une fenêtre à part
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 - Visualiser dans une fenêtre à part
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 - Visualiser dans une fenêtre à part
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.

Envoyer le billet « Delegate sur des propriétés en C# » dans le blog Viadeo Envoyer le billet « Delegate sur des propriétés en C# » dans le blog Twitter Envoyer le billet « Delegate sur des propriétés en C# » dans le blog Google Envoyer le billet « Delegate sur des propriétés en C# » dans le blog Facebook Envoyer le billet « Delegate sur des propriétés en C# » dans le blog Digg Envoyer le billet « Delegate sur des propriétés en C# » dans le blog Delicious Envoyer le billet « Delegate sur des propriétés en C# » dans le blog MySpace Envoyer le billet « Delegate sur des propriétés en C# » dans le blog Yahoo

Catégories
DotNET , C#

Commentaires

  1. Avatar de cboun94
    • |
    • permalink
    Pour des questions de performance, je vous propose d'utiliser les Expressions générées à l'exécution et compilées avant l'usage. Associée à un système de cache Type/Nom de propriété cette méthode permet d'atteindre des performances extrêmement proches d'un appel direct aux propriétés.
  2. Avatar de François DORIN
    • |
    • permalink
    Tardivement, je viens de faire quelques tests. L'usage d'un système de cache n'est ici pas intéressant et pénalise les performances au lieu de les améliorer. Les expressions sont ici trop simples pour qu'il y ait un bénéfice. Et le système de cache n'était pas thread-safe. Donc pour une solution thread-safe, la pénalité serait encore plus importante.