创建可在运行时计算的表达式

时间:2013-12-29 19:45:49

标签: c# .net linq reflection expression

我有一个产品列表,我需要创建表达式树,可以持久化并随后检索和执行。这适用于客户端计算构建器。

我是Expressions的新手,虽然我已经阅读了大量合理的文档,但这里的学习曲线有点陡峭。我想要的是能够积累PropertyExpression和Operand对开始。这是我到目前为止所做的,不确定我是否正确构建了它。

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

namespace ConsoleApplication1
{
    public enum ProductType { Perishable, Fixed, Miscellaneous }    
    public enum OperandType { Addition, Sunbtraction, Multiplication, Division }

    public class Product
    {
        public string Name { get; set; }
        public ProductType Type { get; set; }
        public float Price { get; set; }
    }

    public class Configuration
    {
        public Dictionary<string, float> Dictionary { get; set; }
    }

    public class Operand
    {
        public OperandType Type { get; set; }
    }

    public class CalculationPair<TEntityType, TProperty>
        where TEntityType: class
        where TProperty: struct
    {
        public ValueTypeProperty<TEntityType, TProperty> Left { get; set; }
        public Operand Operand { get; set; }
        public ValueTypeProperty<TEntityType, TProperty> Right { get; set; }

        // How to specify TResult as an [out] parameter?
        public TResult Calculate<TResult> ()
            where TResult: struct
        {
            TResult result = default(TResult);

            if (this.Operand.Type == OperandType.Multiplication)
            {
                // How to execute the expression?
                //result = this.Left * this.Right;
            }

            return (result);
        }
    }

    public class ValueTypeProperty<TEntityType, TProperty>
        where TEntityType: class
        where TProperty: struct
    {
        public string Name { get; set; }

        public Expression<Func<TEntityType, TProperty>> PropertyExpression { get; set; }
    }

    public class ProductPriceProperty:
        ValueTypeProperty<Product, float>
    {
    }

    public static class Program
    {
        public static void Main ()
        {
            Configuration config = new Configuration();
            List<Product> products = new List<Product>();

            config.Dictionary.Add("ExportFactor", 80);
            config.Dictionary.Add("ChannelMargin", 100);

            products.Add(new Product() { Name = "1", Type = ProductType.Fixed, Price = 10 });
            products.Add(new Product() { Name = "2", Type = ProductType.Miscellaneous, Price = 20 });
            products.Add(new Product() { Name = "3", Type = ProductType.Perishable, Price = 30 });

            foreach (var product in products)
            {
                if (product.Type == ProductType.Fixed)
                {
                    CalculationPair<Product, float> calculation = new CalculationPair<Product, float>()
                    {
                        Left = new ProductPriceProperty() { Name = "Product Price", PropertyExpression = (entity => entity.Price) },
                        Operand = new Operand() { Type = OperandType.Multiplication },
                        Right = new ProductPriceProperty() { Name = "ExportFactor", PropertyExpression = (entity => config.Dictionary ["ExportFactor"]) },
                    };

                    // Calculation needs to be persisted to be retrieved later.
                    // ???!

                    // Once calculation has been reconstruction from the persistence layer, it needs to be executed.
                    product.Price = calculation.Calculate<float>();
                }
            }
        }
    }
}

更新:按照优先顺序,我正在努力解决这个问题:

  • 如何在CalculationPair.Calculate<TReult>()函数中执行表达式?
  • 如何在CalculationPair.Calculate<TReult>()函数中将TResult指定为[out]参数?
  • 如何持续计算表达式并在以后检索它?

1 个答案:

答案 0 :(得分:1)

正如Jon所说,你可以使用通常的表达式tress,或者你可以使用这个代码中的闭包匿名方法:

public class CalculationPair<TEntityType, TProperty>
    where TEntityType : class
    where TProperty : struct
{
    // not sure that first three properties are needed here
    public ValueTypeProperty<TEntityType, TProperty> Left { get; set; }
    public Operand Operand { get; set; }
    public ValueTypeProperty<TEntityType, TProperty> Right { get; set; }

    // closure method
    public Func<TEntityType, TProperty> Calculator { get; set; }
}

以下是使用它的Main方法的一部分:

foreach (var product in products)
{
    if (product.Type == ProductType.Fixed)
    {
        CalculationPair<Product, float> calculation = new CalculationPair<Product, float>()
        {
            Left = new ProductPriceProperty() { Name = "Product Price", PropertyExpression = (entity => entity.Price) },
            Operand = new Operand() { Type = OperandType.Multiplication },
            Right = new ProductPriceProperty() { Name = "ExportFactor", PropertyExpression = (entity => config.Dictionary["ExportFactor"]) },

            // only this property is needed, and it will handle reference to config object in the closure
            Calculator = (entity) => entity.Price * config.Dictionary["ExportFactor"]
        };

        // Once calculation has been reconstruction from the persistence layer, it needs to be executed.
        product.Price = calculation.Calculator(product);
    }
}

在该示例中没有表达式树,只是通常的闭包方法。

<强> UPDATE1

左节点和右节点表达式的问题在于,每个此表达式都链接到自己的实体参数,而不是我们创建的指向真实实体对象的ParameterExpression,因此我们需要用ExpressionVisitor将旧的一个重写为新的一个。它用于解析和重写需求。

以下是该重写者的代码:

public class ParameterRewriter : ExpressionVisitor
{
    private readonly ParameterExpression _expToRewrite;

    public ParameterRewriter(ParameterExpression expToRewrite)
    {
        this._expToRewrite = expToRewrite;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        // we just use type checking to understand that it's our parameter, and we replace it with new one
        if (node.Type == this._expToRewrite.Type) return this._expToRewrite;
        return base.VisitParameter(node);
    }
}

这是CalculationPair类:

public class CalculationPair<TEntityType, TProperty>
    where TEntityType : class
    where TProperty : struct
{
    public ValueTypeProperty<TEntityType, TProperty> Left { get; set; }
    public Operand Operand { get; set; }
    public ValueTypeProperty<TEntityType, TProperty> Right { get; set; }

    public TResult Calculate<TResult>(TEntityType entity)
        where TResult : struct
    {
        TResult result = default(TResult);

        var prop = Expression.Parameter(typeof(TEntityType), "param");
        var visitor = new ParameterRewriter(prop);
        var leftExp = visitor.Visit(Left.PropertyExpression.Body);
        var rightExp = visitor.Visit(Right.PropertyExpression.Body);

        Expression body;

        switch (this.Operand.Type)
        {
            case OperandType.Multiplication:
                body = Expression.Multiply(leftExp, rightExp);
                break;
            case OperandType.Addition:
                body = Expression.Add(leftExp, rightExp);
                break;
            case OperandType.Division:
                body = Expression.Divide(leftExp, rightExp);
                break;
            case OperandType.Sunbtraction:
                body = Expression.Subtract(leftExp, rightExp);
                break;
            default:
                throw new Exception("Unknown operand type");
        }

        var lambda = Expression.Lambda<Func<TEntityType, TResult>>(body, prop);

        // compilation is long operation, so you might need to store this Func as property and don't compile it each time
        var func = lambda.Compile();
        result = func(entity);

        return (result);
    }
}

用法相同

product.Price = calculation.Calculate<float>(product);