4 votes

Créer un accesseur de propriété profonde sûr à partir de Lambda avec Expressions

Mon objectif est d'utiliser les lambdas pour créer un objet de liaison de propriété qui peut récupérer en toute sécurité la valeur d'une propriété profonde. Par sûr, il renvoie la valeur par défaut du type de propriété si l'une des propriétés précédentes est nulle plutôt que de lancer une exception de référence nulle.

La signature de la méthode :

public static Func<TO, TP> BuildSafeAccessor<TO, TP>(this Expression<Func<TO, TP>> propertyExpression) where TO: class
{
}

*Edit : Clarifier ma question

Donc si j'appelle :

var safeAccessor = BuildSafeAccessor<Person>(p => p.Address.Zip);

Lorsque safeAccessor est appelé, sa logique serait la suivante :

if (p.Address == null)
    return default(TP);
return p.Address.Zip;

3voto

svick Points 81772

L'observation clé ici est que vous n'avez pas besoin de "l'expression ifFalse déjà entièrement créée", vous pouvez la construire de manière récursive.

Le code pourrait ressembler à ceci :

public static Func<TO, TP> BuildSafeAccessor<TO, TP>(Expression<Func<TO, TP>> propertyExpression)
{
    var properties = GetProperties(propertyExpression.Body);
    var parameter = propertyExpression.Parameters.Single();
    var nullExpression = Expression.Constant(default(TP), typeof(TP));

    var lambdaBody = BuildSafeAccessorExpression(parameter, properties, nullExpression);
    var lambda = Expression.Lambda<Func<TO, TP>>(lambdaBody, parameter);

    return lambda.Compile();
}

private static Expression BuildSafeAccessorExpression(Expression init, IEnumerable<PropertyInfo> properties, Expression nullExpression)
{
    if (!properties.Any())
        return init;

    var propertyAccess = Expression.Property(init, properties.First());
    var nextStep = BuildSafeAccessorExpression(propertyAccess, properties.Skip(1), nullExpression);

    return Expression.Condition(
        Expression.ReferenceEqual(init, Expression.Constant(null)), nullExpression, nextStep);
}

private static IEnumerable<PropertyInfo> GetProperties(Expression expression)
{
    var results = new List<PropertyInfo>();

    while (expression is MemberExpression)
    {
        var memberExpression = (MemberExpression)expression;
        results.Add((PropertyInfo)memberExpression.Member);
        expression = memberExpression.Expression;
    }

    if (!(expression is ParameterExpression))
        throw new ArgumentException();

    results.Reverse();

    return results;
}

(Notez que ce code utilise LINQ de manière inefficace, pour le rendre plus lisible).

Si vous l'exécutez sur la lambda a => a.B.C.D il créera une expression (montrant le résultat de la fonction ToString() sur elle) :

a => IIF((a == null), null, IIF((a.B == null), null, IIF((a.B.C == null), null, a.B.C.D)))

Remarquez que cela permet d'accéder a.B jusqu'à trois fois. Si cette propriété est lente ou a des effets secondaires, cela peut être un problème. Si c'est un problème pour vous, je pense que vous devriez utiliser Block avec des variables locales.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X