c# - 使用表达式从 Lambda 创建安全的深层属性访问器

标签 c# expression-trees

我的目标是使用 Lambdas 创建一个属性绑定(bind)对象,该对象可以安全检索深层属性值。安全起见,如果先前的属性之一为 null,它会返回属性类型的默认值,而不是抛出 null 引用异常。

方法签名:

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

*编辑:澄清我的问题

所以如果我调用:

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

当调用 safeAccessor 时,它的逻辑如下:

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

最佳答案

这里的关键观察是您不需要“已经完全创建的 ifFalse 表达式”,您可以递归地构建它。

代码可能是这样的:

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;
}

(请注意,此代码低效地使用了 LINQ,以使其更具可读性。)

如果您在 lambda a => a.B.C.D 上运行它,它将创建一个表达式(显示 ToString() 的结果):

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

请注意,这最多访问 a.B 三次。如果该属性很慢或有副作用,那可能是个问题。如果这对您来说是个问题,我认为您需要将 Block 与局部变量一起使用。

关于c# - 使用表达式从 Lambda 创建安全的深层属性访问器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14381458/

相关文章:

c# - 如何在 linq 查询中从日期截断时间部分?

c# - 在表达式中使用 string.Compare(a, b)

c# - 端点 x.x.x.x :port serving hashslot nnnn is not reachable at this point of time

c# - "IntConverter"的实例存储在哪里?

.net - 根据属性类型/值推迟子验证器的选择

c# - Lambda 参数不在范围内——在构建二进制 lambda 表达式时

c# - 可访问 'this' 的非静态表达式<Func<X>>

c# - 如何设置默认值(TSource)

c# - 在单向 WCF 调用后调用服务代理 block 上的 Close()

c# - 在异步方法中验证参数