【问题标题】:Convert Expression<Func<TDerived, out TResult>> to Expression<Func<TBase, out TResult>>将 Expression<Func<TDerived, out TResult>> 转换为 Expression<Func<TBase, out TResult>>
【发布时间】:2015-07-01 12:56:20
【问题描述】:

标题中都说了,更确切地说,我正在寻找一种方法来转换

Expression&lt;Func&lt;TDerived, out bool&gt;&gt;Expression&lt;Func&lt;TBase, out bool&gt;&gt;

使用 TDerived 从 TBase 派生。

我怎样才能做到这一点?

【问题讨论】:

  • 不知道可以直接转换。如果Func&lt;TDerived&gt; 引用了不属于基类的派生类的属性怎么办?
  • 你到底想做什么?为什么要“转换”表达式?
  • @Luaan 我正在使用实体框架,我将谓词作为 lambdas 传递给必须同时处理派生实体和基础实体的函数,但基础实体与派生的。
  • @Luaan 我的意思是谓词,不包括
  • 好的,在这种情况下,您将不得不采用 Xanatos 的解决方案 - 遍历整个表达式树并修复它。或者只是将表达式分成两个 - 一个用于基础,一个仅用于派生中的新属性。

标签: c# entity-framework lambda expression-trees


【解决方案1】:

给定一个像这样的Expression 替换器:

using System;
using System.Collections.Generic;
using System.Linq.Expressions;

// A simple expression visitor to replace some nodes of an expression 
// with some other nodes. Can be used with anything, not only with
// ParameterExpression
public class SimpleExpressionReplacer : ExpressionVisitor
{
    public readonly Dictionary<Expression, Expression> Replaces;

    public SimpleExpressionReplacer(Expression from, Expression to)
    {
        Replaces = new Dictionary<Expression, Expression> { { from, to } };
    }

    public SimpleExpressionReplacer(Dictionary<Expression, Expression> replaces)
    {
        // Note that we should really clone from and to... But we will
        // ignore this!
        Replaces = replaces;
    }

    public SimpleExpressionReplacer(IEnumerable<Expression> from, IEnumerable<Expression> to)
    {
        Replaces = new Dictionary<Expression, Expression>();

        using (var enu1 = from.GetEnumerator())
        using (var enu2 = to.GetEnumerator())
        {
            while (true)
            {
                bool res1 = enu1.MoveNext();
                bool res2 = enu2.MoveNext();

                if (!res1 || !res2)
                {
                    if (!res1 && !res2)
                    {
                        break;
                    }

                    if (!res1)
                    {
                        throw new ArgumentException("from shorter");
                    }

                    throw new ArgumentException("to shorter");
                }

                Replaces.Add(enu1.Current, enu2.Current);
            }
        }
    }

    public override Expression Visit(Expression node)
    {
        Expression to;

        if (node != null && Replaces.TryGetValue(node, out to))
        {
            return base.Visit(to);
        }

        return base.Visit(node);
    }
}

现在我们可以了

public class Base
{
    public int ValueBase { get; set; }
}

public class Derived : Base
{
    public int ValueDerived { get; set; }
}

还有一个

Expression<Func<Derived, bool>> exp = x => x.ValueBase == 0;

然后

ParameterExpression parOld = exp.Parameters[0];
ParameterExpression parNew = Expression.Parameter(typeof(Base));

// Replace the parOld with the parNew
Expression body2 = new SimpleExpressionReplacer(parOld, parNew).Visit(exp.Body);

// Note that we have to rebuild the Expression.Lambda<>
Expression<Func<Base, bool>> expNew = Expression.Lambda<Func<Base, bool>>(body2, parNew);

这将产生一个

Expression<Func<Base, bool>> exp = x => x.ValueBase == 0;

请注意,如果您想这样做:

Expression<Func<Derived, bool>> exp = x => x.ValueDerived == 0;

Expression<Func<Base, bool>> exp = x => ((Derived)x).ValueDerived == 0;

那么你需要类似的东西:

ParameterExpression parOld = exp.Parameters[0];
ParameterExpression parNew = Expression.Parameter(typeof(Base));
UnaryExpression convert = Expression.Convert(parNew, typeof(Derived));

Expression body2 = new SimpleExpressionReplacer(parOld, convert).Visit(exp.Body);
Expression<Func<Base, bool>> expNew = Expression.Lambda<Func<Base, bool>>(body2, parNew);

【讨论】:

    【解决方案2】:

    您需要包装内部表达式。类似的东西

    var argument = Expression.Parameter(typeof(TDerived));
    
    Expression.Lambda<Func<TDerived, bool>>
    (
      Expression.Invoke(innerExpression, argument),
      argument
    );
    

    当然,根据方向,您可能需要对 innerExpression 的参数进行显式强制转换 - 这很简单,只需使用 Expression.Cast

    编辑:

    为了适应您的编辑,倒置变体:

    var argument = Expression.Parameter(typeof(TBase));
    
    Expression.Lambda<Func<TBase, bool>>
    (
      Expression.Invoke(innerExpression, Expression.Convert(argument, typeof(TDerived))),
      argument
    );
    

    请注意,这显然只有在参数的运行时类型派生自 TDerived 时才有效。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多