我有一个产品列表,我需要创建表达式树,可以持久化并随后检索和执行。这适用于客户端计算构建器。
我是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]参数?答案 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);