Mi diverto con le espressioni e compare una domanda: genera un'eccezione che non immagino.
Ho un input: una semplice formula matematica, ad esempio 2*x+3
, e voglio creare un albero di espressioni per questo. Quindi scrivo questo codice
using System;
using System.Linq.Expressions;
namespace ConsoleApplication50
{
class Program
{
static void Main()
{
string s = "3*x^2+2/5*x+4";
Expression<Func<double, double>> expr = MathExpressionGenerator.GetExpression(s);
Console.WriteLine(expr);
var del = expr.Compile();
Console.WriteLine(del(10));
}
}
static class MathExpressionGenerator
{
public const string SupportedOps = "+-*/^";
private static readonly ParameterExpression Parameter = Expression.Parameter(typeof(double), "x");
public static Expression<Func<double, double>> GetExpression(string s)
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(double), "x");
Expression result = GetExpressionInternal(s);
return Expression.Lambda<Func<double, double>>(result, parameterExpression);
}
private static Expression GetExpressionInternal(string s)
{
double constant;
if (s == "x")
return Parameter;
if (double.TryParse(s, out constant))
return Expression.Constant(constant, typeof(double));
foreach (char op in SupportedOps)
{
var split = s.Split(new[] { op }, StringSplitOptions.RemoveEmptyEntries);
if (split.Length > 1)
{
var expression = GetExpressionInternal(split[0]);
for (int i = 1; i < split.Length; i++)
{
expression = RunOp(expression, GetExpressionInternal(split[i]), op);
}
return expression;
}
}
throw new NotImplementedException("never throws");
}
private static Expression RunOp(Expression a, Expression b, char op)
{
switch (op)
{
case '+':
return Expression.Add(a, b);
case '-':
return Expression.Subtract(a, b);
case '/':
return Expression.Divide(a, b);
case '*':
return Expression.Multiply(a, b);
case '^':
return Expression.Power(a, b);
}
throw new NotSupportedException();
}
}
}
ma ottengo un errore:
Eccezione non gestita: System.InvalidOperationException: variabile 'x' di tipo 'Sys tem.Double' referenziata dall'ambito '', ma non è definita su System.Linq.Expressions.Compiler.VariableBinder.Reference (nodo ionico ParameterExpress, memoria VariableStorageKind) a System.Linq.Expressions.Compiler.VariableBinder.VisitParameter (nodo prerezione ParameterEx) a System.Linq.Expressions.ParameterExpression.Accept (visita ExpressionVisitor o) a ... e così via
Per favore, consiglio, come può essere risolto? Qui ho un singolo parametro globale e referenziamento così non ho idea, perché dice questa roba.
Il problema con il tuo codice è che hai due istanze del parametro x
. Uno di questi è il campo statico privato che viene utilizzato attraverso la generazione di espressioni e il secondo è quello che crei e utilizzi nella creazione di Lambda.
Se hai ParameterExpression, dovresti usare la stessa istanza nell'espressione e passare la stessa istanza in generazione Lambda, altrimenti fallirà come nel tuo esempio.
funzionerà correttamente se rimuovi parameterExpression
e utilizzerai un campo Parameter
privato come questo:
public static Expression<Func<double, double>> GetExpression(string s)
{
Expression result = GetExpressionInternal(s);
return Expression.Lambda<Func<double, double>>(result, Parameter);
}
Esempio di lavoro in. NetFiddle - https://dotnetfiddle.net/Onw0Hy
static void Main(string[] args)
{
var str = @"3*x^2+2/5*x+4";
str = Transform(str);
var param = Expression.Parameter(typeof (double), "x");
var expression = System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] {param}, null, str);
var exp10 = expression.Compile().DynamicInvoke(10);
Console.WriteLine(exp10);
}
public const string SupportedOps = "+-*/";//separators without ^
private static string Transform(string expression)
{
//replace x^y with Math.Pow(x,y)
var toBeReplaced = expression.Split(SupportedOps.ToCharArray()).Where(s => s.Contains("^"));
var result = expression;
return toBeReplaced.Aggregate(expression, (current, str) => current.Replace(str, string.Format("Math.Pow({0})", str.Replace('^', ','))));
//OR
//foreach (var str in toBeReplaced)
//{
// result =result.Replace(str, string.Format("Math.Pow({0})", str.Replace('^', ',')));
//}
//return result;
}