C#中如何将字符串表达式(如a + b * 2)解析为函数?
在C#里要把像a + b * 2这种字符串表达式转成可执行函数,有几种实用的方案,我给你逐个讲清楚:
方法一:用Roslyn表达式树(推荐,原生官方支持)
这是微软官方的编译器平台方案,稳定性和兼容性拉满,而且不需要额外的第三方依赖(除了一个轻量的NuGet包)。核心思路是把字符串表达式解析成表达式树,再编译成可直接调用的委托。
首先得安装NuGet包:Microsoft.CodeAnalysis.CSharp.Scripting
然后看代码示例:
using Microsoft.CodeAnalysis.CSharp.Scripting; using Microsoft.CodeAnalysis.Scripting; // 泛型方法,支持自定义参数类型和返回值类型 public static Func<T1, T2, TResult> CreateExpressionFunc<T1, T2, TResult>(string expression) { // 配置脚本选项,添加必要的引用和命名空间 var scriptOptions = ScriptOptions.Default .AddReferences(typeof(object).Assembly) .AddImports("System"); // 用元组作为全局变量容器,对应表达式里的a、b参数 var script = CSharpScript.Create<Func<T1, T2, TResult>>( $"return ({expression});", scriptOptions, globalsType: typeof((T1 a, T2 b))); // 编译并获取可执行委托 var compiledFunc = script.RunAsync().Result.ReturnValue; return compiledFunc; } // 调用示例 var calculateFunc = CreateExpressionFunc<double, double, double>("a + b * 2"); double result = calculateFunc(3, 4); // 计算结果:3 + 4*2 = 11 Console.WriteLine(result);
如果你的表达式有更多参数,只需要扩展元组或者自定义一个类作为全局变量容器就行,灵活性很高。
方法二:动态编译成内存程序集
这个方案是把表达式包装成一个静态类的方法,编译成内存中的临时程序集,再通过反射获取方法并创建委托。优点是不需要额外的NuGet包(系统自带的类库就能实现),缺点是代码相对繁琐一点。
代码示例:
using System.CodeDom.Compiler; using System.Reflection; using System.Linq.Expressions; using Microsoft.CSharp; public static Delegate CreateCompiledFunction(string expression, Type[] paramTypes, Type returnType) { // 生成参数名,比如p0、p1对应表达式里的a、b var paramNames = Enumerable.Range(0, paramTypes.Length) .Select(i => $"p{i}") .ToArray(); // 生成方法的参数声明 var paramDeclarations = string.Join(", ", paramNames.Select((name, idx) => $"{paramTypes[idx].FullName} {name}")); // 拼接完整的C#代码 var codeSnippet = $@" using System; public static class ExprEvaluator {{ public static {returnType.FullName} Calculate({paramDeclarations}) {{ return ({expression}); }} }}"; // 配置编译参数,生成内存程序集 var compilerParams = new CompilerParameters { GenerateInMemory = true, TreatWarningsAsErrors = false }; compilerParams.ReferencedAssemblies.Add("System.dll"); // 编译代码 var csharpProvider = new CSharpCodeProvider(); var compileResult = csharpProvider.CompileAssemblyFromSource(compilerParams, codeSnippet); // 处理编译错误 if (compileResult.Errors.HasErrors) { var errorMsg = string.Join("\n", compileResult.Errors.Cast<CompilerError>().Select(e => e.ErrorText)); throw new InvalidOperationException($"编译失败:{errorMsg}"); } // 获取编译后的方法并创建委托 var assembly = compileResult.CompiledAssembly; var evaluateMethod = assembly.GetType("ExprEvaluator").GetMethod("Calculate"); return Delegate.CreateDelegate(Expression.GetFuncType(paramTypes.Concat(new[] { returnType }).ToArray()), evaluateMethod); } // 调用示例 var compiledFunc = (Func<double, double, double>)CreateCompiledFunction( "a + b * 2", new[] { typeof(double), typeof(double) }, typeof(double)); double result = compiledFunc(3, 4); // 同样得到11 Console.WriteLine(result);
方法三:使用第三方表达式解析库(比如NCalc)
如果你的表达式比较复杂,或者不想折腾原生的编译逻辑,可以用专门的表达式解析库,比如NCalc。它轻量易用,支持运算符优先级、参数替换、甚至自定义函数,而且安全性更高(不会执行任意C#代码)。
首先安装NuGet包:NCalc
代码示例:
using NCalc; public static Func<T1, T2, TResult> CreateNCalcFunc<T1, T2, TResult>(string expression) { return (param1, param2) => { var expr = new Expression(expression); // 设置表达式中的参数值 expr.Parameters["a"] = param1; expr.Parameters["b"] = param2; // 执行表达式并返回结果 return (TResult)expr.Evaluate(); }; } // 调用示例 var ncalcFunc = CreateNCalcFunc<double, double, double>("a + b * 2"); double result = ncalcFunc(3, 4); // 11 Console.WriteLine(result);
一些注意事项
- 参数名对应:不管用哪种方案,表达式里的参数名(比如
a、b)要和代码中设置的参数名一致,否则会报错; - 安全问题:如果表达式来自用户输入,要格外小心!Roslyn和动态编译方案会执行任意C#代码,存在注入风险。这种场景下优先用NCalc这类只解析表达式的库,或者对用户输入做严格的校验;
- 性能差异:Roslyn表达式树编译后的委托调用性能和原生方法几乎一致;动态编译第一次有编译开销,后续调用很快;NCalc每次调用会解析表达式,性能稍弱但足够应对大多数业务场景。
内容的提问来源于stack exchange,提问作者Nikitc
相关产品推荐
相关产品推荐

