Sprache: C#
Ausführung von einfachen mathematischen Ausdrücken durch inline Compilierung.
Verwendung:
[code]
var val = Calculate("10 + 20 * 2 / 20");
Console.WriteLine(val);
var val2 = Calculate("(10 + 20) * 2 / 2");
Console.WriteLine(val2);
[/code]
static double Calculate(string expression)
{
var options = new Dictionary<string, string>() { { "CompilerVersion", "v3.5" } };
var provider = new CSharpCodeProvider();
var dlls = new[] { "mscorlib.dll", "System.Core.dll" };
var tempFile = Path.GetTempFileName() + ".dll";
var param = new CompilerParameters(dlls, tempFile, true);
param.GenerateInMemory = true;
param.GenerateExecutable = false;
var code = "using System; " +
"public class Calculator { " +
" public static double Run() { " +
" return " + expression + "; " +
" } " +
"}";
CompilerResults result = provider.CompileAssemblyFromSource(param, code);
if(result.Errors.Count > 0 ){
var errors = string.Join(" -> ", result.Errors.Cast<CompilerError>().Select(e => e.ErrorText));
throw new ArgumentException("expression is invalid. Compiler Result: " + errors);
}
var type = result.CompiledAssembly.GetTypes().First(t => t.Name == "Calculator");
var calcResult = (double)type.GetMethod("Run").Invoke(null, null);
return calcResult;
}
static double Calculate(string expression)
{
var options = new Dictionary<string, string>() { { "CompilerVersion", "v3.5" } };
var provider = new CSharpCodeProvider();
var dlls = new[] { "mscorlib.dll", "System.Core.dll" };
var tempFile = Path.GetTempFileName() + ".dll";
var param = new CompilerParameters(dlls, tempFile, true);
param.GenerateInMemory = true;
param.GenerateExecutable = false;
var code = "using System; " +
"public class Calculator { " +
" public static double Run() { " +
" return " + expression + "; " +
" } " +
"}";
CompilerResults result = provider.CompileAssemblyFromSource(param, code);
if(result.Errors.Count > 0 ){
var errors = string.Join(" -> ", result.Errors.Cast<CompilerError>().Select(e => e.ErrorText));
throw new ArgumentException("expression is invalid. Compiler Result: " + errors);
}
var type = result.CompiledAssembly.GetTypes().First(t => t.Name == "Calculator");
var calcResult = (double)type.GetMethod("Run").Invoke(null, null);
return calcResult;
}
Alte URL:
/snippet/math-evaluator/11022
Danke dafür. Ich habe es für meine Bedürfnisse angepasst:
– ‚dynamic‘ statt ‚double‘, damit lässt sich Alles verarbeiten
– als Klasse für die Initialsierungen, damit die nicht ständig neu gemacht werden müssen
– StringBuilder genutzt um die Expression zusammenzusetzen
– Bei Fehler Rückgabe wert von ‚Null‘ statt eine Ausnahme
Wichtig: die Debuginformation nicht(!) einschließen, sonst klappt es dann nicht mit allen Typen.
Bei Interesse Nachricht an mich.
Da werd ich bekloppt!
Ich habe gerade nachträglich über den Wettbewerb und meine Einreichung geblogt. Da checke ich doch danach meine Mails und eine führt mich hierhin 🙂
Würde gerne dein Snippet sehen und wenn du kein Problem damit hast, dann als Update auf dem Blog veröffentlichen:
http://dev-things.net
Einfach melden, würde mich freuen!
PS: schon gevoted? 🙂
Hier sieht man sehr schön den Zeitgeist von 2015: „einfach den Ausdruck als C#-Code zusammenbauen und per CodeDOM inline kompilieren“. Für Spielereien ok, für produktiven Einsatz heute aber in mehreren Dimensionen problematisch: Sicherheitsrisiko durch Code-Injection, hoher Overhead pro Auswertung, schlechte Cloud-/Container-Tauglichkeit und faktisch Windows-/Full-Framework-lastig.
[b]Analyse nach heutigen Kriterien:[/b]
– [u]Sicherheit[/u]: expression wird ungefiltert in „return “ + expression + „;“ eingebaut. Das ist Code-Injection by design und erlaubt beliebige C#-Ausdrücke, nicht nur Mathematik. Sobald der Input nicht 100% trusted ist, ist das ein Volltreffer-Risiko.
– [u]Cloud/Container/Cross-Platform[/u]: CSharpCodeProvider/CodeDOM ist in modernen .NET-Deployments (Container, Linux, trimming, single-file, AOT) oft unzuverlässig oder nicht gewünscht. Der Ansatz hängt außerdem an Compiler-Tooling und Dateisystem (Temp-File).
– [u]Performance[/u]: Pro Calculate-Aufruf wird kompiliert, Assemblies werden erzeugt, Reflection wird genutzt. Das ist Größenordnungen langsamer als ein Parser/Interpreter. Zusätzlich ist tempFile-Handling I/O-lastig.
– [u]Memory-Allokationen[/u]: Code-String-Konkatenation, Compiler-Objekte, CompilerResults, Assembly-Laden, Reflection-Infos erzeugen viel unnötigen Heap-Druck.
– [u]Robustheit[/u]: Fehlertexte werden als String zusammengebaut, aber der eigentliche Kontext (Position im Ausdruck) fehlt. Außerdem werden Ressourcen nicht strikt über using/Dispose abgesichert (Provider/CompilerParameters/Tempfile-Lifecycle).
– [u]Thread-Safety[/u]: Der gezeigte Code hat keinen Shared State und ist daher thread-safe, skaliert aber wegen Compiler-Overhead und globalen Ressourcen (Compiler/Temp) unter Parallelität schlecht.
– [u]Standard-.NET-Ansatz[/u]: Für Mathe-Ausdrücke ist ein Parser (Tokenize + Shunting-Yard + Stack) oder eine etablierte Library der sinnvolle Weg. Kein Runtime-Compile für untrusted Strings.
[b]Modernisierte Variante (sicherer Math-Parser, nur + – * / und Klammern, keine Code-Ausführung):[/b]
[code]
using System;
using System.Collections.Generic;
using System.Globalization;
public static class MathEvaluator
{
public static double Calculate(string expression)
{
if (expression is null) throw new ArgumentNullException(nameof(expression));
var values = new Stack();();
var ops = new Stack
int i = 0;
while (i < expression.Length) { char c = expression[i]; if (char.IsWhiteSpace(c)) { i++; continue; } if (c == '(') { ops.Push(c); i++; continue; } if (c == ')') { while (ops.Count > 0 && ops.Peek() != ‚(‚) ApplyOp(values, ops.Pop());
if (ops.Count == 0 || ops.Pop() != ‚(‚) throw new FormatException(„Mismatched parentheses.“);
i++;
continue;
}
if (IsOp(c))
{
while (ops.Count > 0 && IsOp(ops.Peek()) && Precedence(ops.Peek()) >= Precedence(c)) ApplyOp(values, ops.Pop());
ops.Push(c);
i++;
continue;
}
int start = i;
bool hasDot = false;
if (c == ‚+‘ || c == ‚-‚)
{
bool unary = start == 0 || PrevNonWs(expression, start) is ‚(‚ or ‚+‘ or ‚-‚ or ‚*‘ or ‚/‘;
if (!unary) throw new FormatException(„Unexpected operator position.“);
i++;
}
while (i < expression.Length) { char d = expression[i]; if (char.IsDigit(d)) { i++; continue; } if (d == '.') { if (hasDot) break; hasDot = true; i++; continue; } break; } string token = expression.Substring(start, i - start); if (!double.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out double number)) throw new FormatException("Invalid number token."); values.Push(number); } while (ops.Count > 0)
{
char op = ops.Pop();
if (op == ‚(‚) throw new FormatException(„Mismatched parentheses.“);
ApplyOp(values, op);
}
if (values.Count != 1) throw new FormatException(„Invalid expression.“);
return values.Pop();
}
private static bool IsOp(char c) => c == ‚+‘ || c == ‚-‚ || c == ‚*‘ || c == ‚/‘;
private static int Precedence(char op) => op == ‚+‘ || op == ‚-‚ ? 1 : 2;
private static void ApplyOp(Stack values, char op)
{
if (values.Count < 2) throw new FormatException("Invalid expression."); double b = values.Pop(); double a = values.Pop(); values.Push(op switch { '+' => a + b,
‚-‚ => a – b,
‚*‘ => a * b,
‚/‘ => a / b,
_ => throw new FormatException(„Unknown operator.“)
});
}
private static char PrevNonWs(string s, int index)
{
for (int j = index – 1; j >= 0; j–) if (!char.IsWhiteSpace(s[j])) return s[j];
return ‚