如何使用防爆pression树安全地访问可空对象的路径? [英] How to use Expression Tree to safely access path of nullable objects?

查看:76
本文介绍了如何使用防爆pression树安全地访问可空对象的路径?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我得到反序列化的XML结果为对象的XSD生成的树,要使用那棵树a.b.c.d.e.f里面的一些深层次的对象,它会给我例外,如果查询路径上的任何节点丢失。

When I get deserialized XML result into xsd-generated tree of objects and want to use some deep object inside that tree a.b.c.d.e.f, it will give me exception if any node on that query path is missing.

if(a.b.c.d.e.f != null)
    Console.Write("ok");

我想避免检查空像这样每一级:

I want to avoid checking for null for each level like this:

if(a != null)
if(a.b != null)
if(a.b.c != null)
if(a.b.c.d != null)
if(a.b.c.d.e != null)
if(a.b.c.d.e.f != null)
    Console.Write("ok");

首先解决方案是实现扩展获取方法,它允许这样的:

First solution is to implement Get extension method that allows this:

if(a.Get(o=>o.b).Get(o=>o.c).Get(o=>o.d).Get(o=>o.e).Get(o=>o.f) != null)
    Console.Write("ok");

第二个解决方案是实现GET(字符串)扩展方法,并使用反射来获取结果来看是这样的:

Second solution is to implement Get(string) extension method and use reflection to get result looking like this:

if(a.Get("b.c.d.e.f") != null)
    Console.Write("ok");

第三解决方案,可以实现ExpandoObject并使用动态类型来获得结果来看是这样的:

Third solution, could be to implement ExpandoObject and use dynamic type to get result looking like this:

dynamic da = new SafeExpando(a);
if(da.b.c.d.e.f != null)
    Console.Write("ok");

但去年2解决方案不给强类型和智能感知的好处。

But last 2 solutions do not give benefits of strong typing and IntelliSense.

我认为最好的可能是第四个解决方案,可与前pression树来实现:

I think the best could be fourth solution that can be implemented with Expression Trees:

if(Get(a.b.c.d.e.f) != null)
    Console.Write("ok");

if(a.Get(a=>a.b.c.d.e.f) != null)
    Console.Write("ok");

我已经实现了第一和第二的解决方案。

I already implemented 1st and 2nd solutions.

下面是解决方案是如何第一次是这样的:

Here is how 1st solution looks like:

[DebuggerStepThrough]
public static To Get<From,To>(this From @this, Func<From,To> get)
{
    var ret = default(To);
    if(@this != null && !@this.Equals(default(From)))
        ret = get(@this);

    if(ret == null && typeof(To).IsArray)
        ret = (To)Activator.CreateInstance(typeof(To), 0);

    return ret;
}

如何实现可能的话4的解决方案?

How to implement 4th solution if possible ?

此外,它会是有趣的,看看如何实现可能的话第3的解决方案。

Also it would be interesting to see how to implement 3rd solution if possible.

推荐答案

所以,起点是创造一个前pression游客。这让我们找到一个特定的前pression内所有成员的访问。这给我们留下了什么为每个成员访问做些什么的问题。

So the starting place is creating an expression visitor. This lets us find all of the member accesses within a particular expression. This leaves us with the question of what to do for each member access.

所以,第一件事就是在该成员被访问的前pression递归参观。从这里,我们可以使用防爆pression.Condition 来创建比较了处理底层的前pression到空<条件块/ code>,并返回如果属实的原始出发前pression如果它不是。

So the first thing is to recursively visit on the expression that the member is being accessed on. From there, we can use Expression.Condition to create a conditional block that compares that processed underlying expression to null, and returns null if true an the original starting expression if it's not.

请注意,我们需要为这两个成员和方法调用实现,但每个过程基本上是相同的。

Note that we need to provide implementations for both Members and method calls, but the process for each is basically identical.

我们还将添加一个检查,以便底层前pression是(这是说,没有实例,它是一个静态的成员),或者如果它是一个非可空类型,我们只使用基本行为来代替。

We'll also add in a check so that of the underlying expression is null (which is to say, there is no instance and it's a static member) or if it's a non-nullable type, that we just use the base behavior instead.

public class MemberNullPropogationVisitor : ExpressionVisitor
{
    protected override Expression VisitMember(MemberExpression node)
    {
        if (node.Expression == null || !IsNullable(node.Expression.Type))
            return base.VisitMember(node);

        var expression = base.Visit(node.Expression);
        var nullBaseExpression = Expression.Constant(null, expression.Type);
        var test = Expression.Equal(expression, nullBaseExpression);
        var memberAccess = Expression.MakeMemberAccess(expression, node.Member);
        var nullMemberExpression = Expression.Constant(null, node.Type);
        return Expression.Condition(test, nullMemberExpression, node);
    }

    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        if (node.Object == null || !IsNullable(node.Object.Type))
            return base.VisitMethodCall(node);

        var expression = base.Visit(node.Object);
        var nullBaseExpression = Expression.Constant(null, expression.Type);
        var test = Expression.Equal(expression, nullBaseExpression);
        var memberAccess = Expression.Call(expression, node.Method);
        var nullMemberExpression = Expression.Constant(null, MakeNullable(node.Type));
        return Expression.Condition(test, nullMemberExpression, node);
    }

    private static Type MakeNullable(Type type)
    {
        if (IsNullable(type))
            return type;

        return typeof(Nullable<>).MakeGenericType(type);
    }

    private static bool IsNullable(Type type)
    {
        if (type.IsClass)
            return true;
        return type.IsGenericType &&
            type.GetGenericTypeDefinition() == typeof(Nullable<>);
    }
}

我们可以创建一个扩展方法,使通话更轻松:

We can then create an extension method to make calling it easier:

public static Expression PropogateNull(this Expression expression)
{
    return new MemberNullPropogationVisitor().Visit(expression);
}

除了一个接受一个lambda,而不是任何前pression,并且可以返回已编译的代表:

As well as one that accepts a lambda, rather than any expression, and can return a compiled delegate:

public static Func<T> PropogateNull<T>(this Expression<Func<T>> expression)
{
    var defaultValue = Expression.Constant(default(T));
    var body = expression.Body.PropogateNull();
    if (body.Type != typeof(T))
        body = Expression.Coalesce(body, defaultValue);
    return Expression.Lambda<Func<T>>(body, expression.Parameters)
        .Compile();
}

需要注意的是,支持的情况下访问成员解析为一个非空值,我们正在改变那些前pressions类型解除他们是空的,使用 MakeNullable 。这是这场决赛的前pression一个问题,因为它需要一个 Func键&LT; T&GT; ,它会不匹配,如果 ŧ是不是也为之一振。因此,尽管这是非常不理想的(理想情况下你不会调用此方法具有非空的 T ,但有支持这个在C#没有什么好办法),我们使用默认值对于该类型,如果需要,合并的最终值。

Note that, to support cases where the accessed member resolves to a non-nullable value, we're changing the type of those expressions to lift them to be nullable, using MakeNullable. This is a problem with this final expression, as it needs to be a Func<T>, and it won't match if T isn't also lifted. Thus, while it's very much non-ideal (ideally you'd never call this method with a non-nullable T, but there's no good way to support this in C#) we coalesce the final value using the default value for that type, if necessary.

(你可以平凡修改此接受一个lambda接受一个参数,并传递一个值,但你可以很容易地接近了这个参数,而不是,所以我认为没有真正的理由。)

(You can trivially modify this to accept a lambda accepting a parameter, and pass in a value, but you can just as easily close over that parameter instead, so I see no real reason to.)

另外值得指出的是,在C#6.0中,当它实际上是释放,我们将有一个实际的空传播史运算符(),使得这一切很不必要。您可以这样写:

It's also worth pointing out that in C# 6.0, when it's actually released, we'll have an actual null propogation operator (?.), making all of this very unnecessary. You'll be able to write:

if(a?.b?.c?.d?.e?.f != null)
    Console.Write("ok");

和恰好有你要找的语义。

and have exactly the semantics you're looking for.

这篇关于如何使用防爆pression树安全地访问可空对象的路径?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆