Mise en cache des délégués d'expression compilés

c# expression-trees lambda

Question

Backstory: Tout au long de notre projet, nous nous retrouvons constamment à l'aide de l'objet System.ComponentModel.DataAnnotations.Validator ainsi que d'attributs permettant de valider les propriétés transmises à notre API. L'objet Validator nécessite de transmettre le nom de la propriété ainsi que la valeur, ce qui est juste, mais nous sommes tous un peu dépassés par le nombre (en constante augmentation) de chaînes magiques transmises pour ces noms de propriété. Non seulement cela comporte un risque d'erreur de frappe du nom de propriété, cela réduit également la maintenabilité dans le cas où l'une de ces propriétés serait renommée (encore une fois, il y en a beaucoup trop).

Pour résoudre automatiquement le nom du membre, j'ai créé cette méthode d'assistance (par souci de simplicité, supposons que je traite uniquement des propriétés de type référence, d'où la contrainte P: class ):

public static P ValidateProperty<T, P>(T obj, Expression<Func<T, P>> member, List<ValidationResult> results, Func<P, P> onValidCallback = null)
    where T : class
    where P : class
{
    var expr = member.Compile();
    var memberName = ((MemberExpression)member.Body).Member.Name;
    var value = expr(obj);

    bool isValid = Validator.TryValidateProperty(value, new ValidationContext(obj) { MemberName = memberName }, results);
    return isValid ? (onValidCallback != null ? onValidCallback(value) : value) : null;
}

Et je l'appelle simplement comme ça:

public static P ValidateProperty<T, P>(T obj, Expression<Func<T, P>> member, List<ValidationResult> results, Func<P, P> onValidCallback = null)
    where T : class
    where P : class
{
    var expr = member.Compile();
    var memberName = ((MemberExpression)member.Body).Member.Name;
    var value = expr(obj);

    bool isValid = Validator.TryValidateProperty(value, new ValidationContext(obj) { MemberName = memberName }, results);
    return isValid ? (onValidCallback != null ? onValidCallback(value) : value) : null;
}

Ce qui fonctionne comme un charme et n'implique aucune chaîne magique.

Un exemple de requête ressemblerait à ceci:

public static P ValidateProperty<T, P>(T obj, Expression<Func<T, P>> member, List<ValidationResult> results, Func<P, P> onValidCallback = null)
    where T : class
    where P : class
{
    var expr = member.Compile();
    var memberName = ((MemberExpression)member.Body).Member.Name;
    var value = expr(obj);

    bool isValid = Validator.TryValidateProperty(value, new ValidationContext(obj) { MemberName = memberName }, results);
    return isValid ? (onValidCallback != null ? onValidCallback(value) : value) : null;
}

Ce qui me préoccupe ici, toutefois, concerne les conséquences sur les performances de member.Compile() . Comme il existe de nombreuses permutations possibles de T, P ( C1, Property1 , C1, Property2 , C2, Property1 etc.), je ne pourrai pas mettre en cache l'expression compilée et l'exécuter lors du prochain appel, à moins que T et P sont du même type, ce qui arrive rarement.

Je pourrais optimiser cela en modifiant le Expression<Func<T, P>> member en Expression<Func<T, object>> member sorte que je ne devrais maintenant que mettre en cache l'expression une fois par type de T (c.-à-d. C1 , C2 , etc. .)

Je me demandais si quelqu'un avait des idées sur une meilleure façon de le faire, ou est-ce que j'essayais de "sur-ingénier" le problème? Existe-t-il un modèle commun pour les situations impliquant de passer sans cesse des ficelles magiques?

Réponse acceptée

Une autre option: Plutôt que d’utiliser des fonctionnalités de C # 6 ou de compiler, utilisez simplement la réflexion. Maintenant, vous négociez une dégradation des performances de compilation pour une dégradation des performances de réflexion (qui est probablement inférieure).

...
var property = (PropertyInfo) ((MemberExpression)member.Body).Member;
var propertyName = property.Name;
var value =  property.GetValue(obj, new object[0]);
...

Réponse populaire

Eh bien, vous avez raison, Expression.Compile vraiment une surcharge de performances importante. Si les performances sont réellement une préoccupation et si l'approche de réflexion mentionnée dans une autre réponse vous concerne toujours, voici comment vous pouvez implémenter la mise en cache des délégués compilée:

public static class ValidateUtils
{
    static readonly Dictionary<Type, Dictionary<string, Delegate>> typeMemberFuncCache = new Dictionary<Type, Dictionary<string, Delegate>>();

    public static P ValidateProperty<T, P>(this T obj, Expression<Func<T, P>> member, List<ValidationResult> results, Func<P, P> onValidCallback = null)
        where T : class
        where P : class
    {
        var memberInfo = ((MemberExpression)member.Body).Member;
        var memberName = memberInfo.Name;
        Func<T, P> memberFunc;
        lock (typeMemberFuncCache)
        {
            var type = typeof(T);
            Dictionary<string, Delegate> memberFuncCache;
            if (!typeMemberFuncCache.TryGetValue(type, out memberFuncCache))
                typeMemberFuncCache.Add(type, memberFuncCache = new Dictionary<string, Delegate>());
            Delegate entry;
            if (memberFuncCache.TryGetValue(memberName, out entry))
                memberFunc = (Func<T, P>)entry;
            else
                memberFuncCache.Add(memberName, memberFunc = member.Compile());
        }
        var value = memberFunc(obj);
        bool isValid = Validator.TryValidateProperty(value, new ValidationContext(obj) { MemberName = memberName }, results);
        return isValid ? (onValidCallback != null ? onValidCallback(value) : value) : null;
    }
}

Btw, je voudrais supprimer P : class contrainte, permettant ainsi de valider aussi des valeurs numériques, date, etc., mais quand même.




Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi