使用 ExpressionVisitor 创建算术公式
Create Arithmetic Formula using ExpressionVisitor
我正在尝试通过来自模型列的 entity framework lambda 创建一个动态公式
public class OutputModel
{
public decimal Result {get;set;}
}
public class TableTest
{
public decimal A {get;set;}
public decimal B {get;set;}
public decimal C {get;set;}
}
Expression<Func<TableTest, OutputModel>> _expr = t => new OutputModel();
TestExpressionVisitor _visitor = new TestExpressionVisitor();
_visitor.Visit(_expr);
var _result = new TempDataContext().TableTests.Select(_expr);
我正在考虑使用表达式访问者来修改结果
public class TestExpressionVisitor : ExpressionVisitor
{
public override Expression Visit(Expression node)
{
return base.Visit(node);
}
protected override Expression VisitMember(MemberExpression node)
{
return base.VisitMember(node);
}
}
但不太确定如何构造一个可以从字符串参数为列执行算术函数的表达式 ({A}+{B}({C}))其中 A、B 和 C 来自 TableTest,并将结果放在 OutputModel.Result 上。我在使用 expressionvisitor 的正确道路上吗?
如有任何帮助,我们将不胜感激。
要扩展我的评论,通常您需要:
- 解析您的输入并将其转换为标记,其中标记只是转换为 C# 对象的字符串的语义组件。
如果我们使用示例 (A+B)*C
,标记将是对象 "Bracket (open), Variable (A), Operator (+), Variable B, Bracket (Close), Operator (*), Variable (C)".
我不会详细说明如何做到这一点,因为这不是问题的一部分,但我曾经使用正则表达式序列编写了一个非常简单但功能强大的分词器,因为这些已经完成了解析所需的大部分艰苦工作。
- 重新排序您的令牌,就像它们需要处理一样,考虑运算符优先规则和括号。这可以通过使用 Dijkstra 的 shunting-yard 算法来完成,请参见 https://en.wikipedia.org/wiki/Shunting-yard_algorithm 的示例。
您重新排序的令牌现在是“变量 (A)、变量 (B)、运算符 (+),变量 (C), 变量 (*).
- 将您的令牌树或队列(无论您选择如何存储)转换为表达式树。
如果是令牌队列,最后一步是:
// for demo purposes I manually fill the list of tokens
// with the tokens in order how they are output by the shunting-yard algorithm
var tokenQueue = new Token[]
{
new VariableToken("A"),
new VariableToken("B"),
new OperatorToken("+"),
new VariableToken("C"),
new OperatorToken("*")
};
var inputParameter = Expression.Parameter(typeof(TableTest));
var expressions = new Stack<Expression>();
foreach (var token in tokenQueue)
{
// transform token to expression by using the helper methods of https://msdn.microsoft.com/de-de/library/system.linq.expressions.expression_methods(v=vs.110).aspx
switch (token)
{
case VariableToken variableToken:
// this will reference the property in your TableTest input specified by the variable name, e.g. "A" will reference TableTest.A
expressions.Push(Expression.Property(inputParameter, variableToken.Name));
break;
case OperatorToken operatorToken:
// This will take two expression from the stack, give these to input to an operator and put the result back onto the queue for use for the next operator
var rightOperand = expressions.Pop();
var leftOperand = expressions.Pop();
if (operatorToken.Name == "+")
{
expressions.Push(Expression.Add(leftOperand, rightOperand));
}
else if (operatorToken.Name == "*")
{
expressions.Push(Expression.Multiply(leftOperand, rightOperand));
}
break;
}
}
// create and fill output model with final expression
var outputModelExpr = Expression.New(typeof(OutputModel).GetConstructor(new[] {typeof(decimal) }), expressions.Single());
// create the lambda expression
// in this example it will have the form: x => return new OutputModel((x.A + x.B) * x.C)
Expression<Func<TableTest, OutputModel>> lambda = Expression.Lambda<Func<TableTest, OutputModel>>(outputModelExpr, inputParameter);
// only for testing purposes: compile it to a function and run it
var calc = lambda.Compile();
var testInput = new TableTest { A = 1, B = 2, C = 3 };
Console.WriteLine(calc(testInput).Result); // returns 9, because (A + B) * C = (1 + 2) * 3 = 9
使用令牌类:
public abstract class Token
{
public string Name { get; protected set; }
}
public class VariableToken : Token
{
public VariableToken(string name) { Name = name; }
}
public class OperatorToken : Token
{
public OperatorToken(string name) { Name = name; }
}
请注意我向 OutputModel 添加了一个构造函数,因为这使得表达式更容易:
public class OutputModel
{
public OutputModel(decimal result) { Result = result; }
public decimal Result {get;set;}
}
我正在尝试通过来自模型列的 entity framework lambda 创建一个动态公式
public class OutputModel
{
public decimal Result {get;set;}
}
public class TableTest
{
public decimal A {get;set;}
public decimal B {get;set;}
public decimal C {get;set;}
}
Expression<Func<TableTest, OutputModel>> _expr = t => new OutputModel();
TestExpressionVisitor _visitor = new TestExpressionVisitor();
_visitor.Visit(_expr);
var _result = new TempDataContext().TableTests.Select(_expr);
我正在考虑使用表达式访问者来修改结果
public class TestExpressionVisitor : ExpressionVisitor
{
public override Expression Visit(Expression node)
{
return base.Visit(node);
}
protected override Expression VisitMember(MemberExpression node)
{
return base.VisitMember(node);
}
}
但不太确定如何构造一个可以从字符串参数为列执行算术函数的表达式 ({A}+{B}({C}))其中 A、B 和 C 来自 TableTest,并将结果放在 OutputModel.Result 上。我在使用 expressionvisitor 的正确道路上吗?
如有任何帮助,我们将不胜感激。
要扩展我的评论,通常您需要:
- 解析您的输入并将其转换为标记,其中标记只是转换为 C# 对象的字符串的语义组件。
如果我们使用示例(A+B)*C
,标记将是对象 "Bracket (open), Variable (A), Operator (+), Variable B, Bracket (Close), Operator (*), Variable (C)".
我不会详细说明如何做到这一点,因为这不是问题的一部分,但我曾经使用正则表达式序列编写了一个非常简单但功能强大的分词器,因为这些已经完成了解析所需的大部分艰苦工作。 - 重新排序您的令牌,就像它们需要处理一样,考虑运算符优先规则和括号。这可以通过使用 Dijkstra 的 shunting-yard 算法来完成,请参见 https://en.wikipedia.org/wiki/Shunting-yard_algorithm 的示例。
您重新排序的令牌现在是“变量 (A)、变量 (B)、运算符 (+),变量 (C), 变量 (*). - 将您的令牌树或队列(无论您选择如何存储)转换为表达式树。
如果是令牌队列,最后一步是:
// for demo purposes I manually fill the list of tokens
// with the tokens in order how they are output by the shunting-yard algorithm
var tokenQueue = new Token[]
{
new VariableToken("A"),
new VariableToken("B"),
new OperatorToken("+"),
new VariableToken("C"),
new OperatorToken("*")
};
var inputParameter = Expression.Parameter(typeof(TableTest));
var expressions = new Stack<Expression>();
foreach (var token in tokenQueue)
{
// transform token to expression by using the helper methods of https://msdn.microsoft.com/de-de/library/system.linq.expressions.expression_methods(v=vs.110).aspx
switch (token)
{
case VariableToken variableToken:
// this will reference the property in your TableTest input specified by the variable name, e.g. "A" will reference TableTest.A
expressions.Push(Expression.Property(inputParameter, variableToken.Name));
break;
case OperatorToken operatorToken:
// This will take two expression from the stack, give these to input to an operator and put the result back onto the queue for use for the next operator
var rightOperand = expressions.Pop();
var leftOperand = expressions.Pop();
if (operatorToken.Name == "+")
{
expressions.Push(Expression.Add(leftOperand, rightOperand));
}
else if (operatorToken.Name == "*")
{
expressions.Push(Expression.Multiply(leftOperand, rightOperand));
}
break;
}
}
// create and fill output model with final expression
var outputModelExpr = Expression.New(typeof(OutputModel).GetConstructor(new[] {typeof(decimal) }), expressions.Single());
// create the lambda expression
// in this example it will have the form: x => return new OutputModel((x.A + x.B) * x.C)
Expression<Func<TableTest, OutputModel>> lambda = Expression.Lambda<Func<TableTest, OutputModel>>(outputModelExpr, inputParameter);
// only for testing purposes: compile it to a function and run it
var calc = lambda.Compile();
var testInput = new TableTest { A = 1, B = 2, C = 3 };
Console.WriteLine(calc(testInput).Result); // returns 9, because (A + B) * C = (1 + 2) * 3 = 9
使用令牌类:
public abstract class Token
{
public string Name { get; protected set; }
}
public class VariableToken : Token
{
public VariableToken(string name) { Name = name; }
}
public class OperatorToken : Token
{
public OperatorToken(string name) { Name = name; }
}
请注意我向 OutputModel 添加了一个构造函数,因为这使得表达式更容易:
public class OutputModel
{
public OutputModel(decimal result) { Result = result; }
public decimal Result {get;set;}
}