一个简单的计算器——使用System.CodeDom来生成代码

本文介绍如何利用C#的System.CodeDom特性,在运行时动态编译并计算数学表达式。通过用户输入函数定义,可以即时生成并计算任意数学表达式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

链接:[url=http://www.codeproject.com/KB/recipes/matheval.aspx]Evaluating Mathematical Expressions by Compiling C# Code at Runtime[/url], by Marcin Cuprjak (aka Vlad Tepes)

本文只是上面链接里的文章的C# 3.0翻新版。主要是引用过来,为了[url=http://rednaxelafx.iteye.com/blog/182762]另一篇文章[/url]的需要而将System.CodeDom部分的例子分离到这边来讲。
下面的代码里用到了C# 3.0的类型推导(var关键字),也用到了C#一直都有的verbatim string,不过JavaEye这里的语法高亮程序显然没能处理好这部分的高亮……将就看吧。

我们总是有使用桌面计算器的需求。很多时候,我都会找一个带有交互式环境的解释器的脚本语言来充当桌面计算器。
当然我们总是可以直接写一个表达式解析器来做一个完整的计算器,但那样太麻烦了。本文所演示的,是直接使用C#的表达式,动态生成一个能运算用户指定的表达式的小程序。

假如说我们要计算一个函数的值:
[code]z = f(x, y)[/code]
但是我们在编译时还不知道f()函数的定义,而且希望由用户在运行时输入函数定义,则我们可以按顺序来做这么几件事:
1、让用户输入一个表达式作为函数的定义
2、把函数在内存里编译
3、让用户输入x和y的值,并调用函数
4、处理函数的返回结果

在.NET里,让现成的编译器来做这件事非常的简单。首先我们定义一个基类,包含有一个计算表达式的函数的stub:
EvaluatorBase.cs
namespace MathEvaluator
{
public class EvaluatorBase
{
public virtual double Eval( double x, double y ) {
return 0.0; // return dummy value for this base class
}
}
}


然后我们定义一个类来实现对表达式的in-memory编译,如下。这里用到了反射和System.CodeDom来生成代码。在Initialize()方法里,假如编译成功则返回真,失败则返回假。留意到“源代码”是一个字符串,中间留了个空,让用户来填充。
Program.cs, part 1
using System;
using System.CodeDom.Compiler;
using Microsoft.CSharp;

namespace MathEvaluator
{
public class MathExpressionEvaluator
{
private EvaluatorBase m_evaluator;

public bool Initialize( string expression ) {
var compiler = new CSharpCodeProvider( );
var options = new CompilerParameters( );

// set compile options
options.GenerateExecutable = false;
options.GenerateInMemory = true;
options.ReferencedAssemblies.Add( "System.dll" );
options.ReferencedAssemblies.Add( this.GetType( ).Assembly.Location );

// set the source code to compile
var source =
@"using System;
using MathEvaluator;
public class UserExpressionEvaluator : MathEvaluator.EvaluatorBase {
public override double Eval( double x, double y ) {
return " + expression + @";
}
}";

// compile the code, on-the-fly
var result = compiler.CompileAssemblyFromSource( options, source );

// print errors
foreach ( var error in result.Errors )
Console.WriteLine( error );

// if compilation sucessed
if ( ( !result.Errors.HasErrors )
&& ( result.CompiledAssembly != null ) ) {
var type = result.CompiledAssembly.GetType(
"UserExpressionEvaluator" );
try {
if ( type != null )
this.m_evaluator =
Activator.CreateInstance( type ) as EvaluatorBase;
} catch ( Exception ex ) {
Console.WriteLine( ex );
return false; // something went wrong with the type
}
return true;
}

// compilation failed
return false;
}

public double Eval( double x, double y ) {
if ( this.m_evaluator != null )
return this.m_evaluator.Eval( x, y );
return 0.0;
}
}


接下来只要使用上面的类就行。在下面的驱动类里与用户交互,进行输入输出,并计算结果:
Program.cs, part 2
    class Program
{
static void Main( string[ ] args ) {
var evaluator = new MathExpressionEvaluator( );
Console.WriteLine( "Enter a math expression,"
+ " with 2 parameters available (x and y): " );
string expression = Console.ReadLine( );
if ( evaluator.Initialize( expression ) ) {
Console.Write( "Enter x: " );
var x = Convert.ToInt32( Console.ReadLine( ).Trim( ) );
Console.Write( "Enter y: " );
var y = Convert.ToInt32( Console.ReadLine( ).Trim( ) );
Console.WriteLine( "The result is: {0}",
evaluator.Eval( x, y ).ToString( ) );
} else {
Console.WriteLine( "The expression is either unsupported"
+ " or contains syntax error(s).");
}
}
}
}


试验一下程序的运行:
[quote][color=darkblue]Enter a math expression, with 2 parameters available (x and y):[/color]
x + Math.Sin(y)
[color=darkblue]Enter x:[/color] 1
[color=darkblue]Enter y:[/color] 2
[color=darkblue]The result is: 1.90929742682568[/color][/quote]
也验证一下出错时会怎样:
[quote][color=darkblue]Enter a math expression, with 2 parameters available (x and y):[/color]
(x + 1)) / y
[color=darkblue]d:\temp\lbfxtper.0.cs(5,39) : error CS1002: 应输入 ;
d:\temp\lbfxtper.0.cs(5,39) : error CS1525: 无效的表达式项“)”
The expression is either unsupported or contains syntax error(s).[/color][/quote]
嘛,我是在简体中文的Windows XP上运行这程序,所以错误提示里出现了中文……


Anyway,作为桌面计算器上面的代码还是太简陋了。本文只是要做个使用System.CodeDom的例子。
在上面的使用场景中,由于我们无法在运行前得知函数的定义,所以有些错误带到了运行时才能发现也不算是坏事。但假如我们使用System.CodeDom生成代码前就能知道完整的源代码是怎样的,那么让源代码放在字符串里显然不够好——类型安全消失了,IDE等开发工具的支持也没了,因为我们所有的待编译代码都在字符串里。
如果有办法既能在运行时才编译代码,又能在编译时就发现语法错误,那就……请期待下一篇文~
### C# 编译生 EXE 可执行文件 #### 使用命令行工具 csc.exe 进行编译 对于简单的 `.cs` 文件,可以利用 Microsoft 提供的命令行编译器 `csc.exe` 来创建独立运行的应用程序。假设有一个名为 `Program.cs` 的源码文件,在命令提示符下输入如下指令即可将其转换为 Windows 控制台应用程序: ```bash csc Program.cs ``` 这将会在同一目录下生一个同名但扩展名为 .exe 的文件[^1]。 如果希望指定输出路径或自定义生的目标名称,则可以通过附加参数实现更精细控制: ```bash csc /out:D:\MyApp\myapplication.exe Program.cs ``` 上述例子中 `/out:` 参数指定了最终可执行文件的位置以及期望的名字。 #### 利用 Visual Studio 开发环境构建项目 当面对较为复杂的解决方案时,借助于功能强大的 IDE —— Visual Studio 是更为高效的选择。新建一个 Console App (.NET Framework 或者 .NET Core/.NET 5+) 类型工程之后,编写好业务逻辑代码保存下来;随后只需点击菜单栏中的 "Build" -> "Build Solution", 就能自动完整个项目的编译过程,并把结果放置到预设的输出位置[^2]。 另外值得注意的是,Visual Studio 不仅支持常规方式下的静态编译,还允许开发者通过编程接口 CodeDom Provider 动态地将字符串形式表示的一段或多段 C#代码即时转化为内存中的 Assembly 对象甚至直接落地为磁盘上的物理文件。下面给出一段简单示例展示如何做到这一点: ```csharp using System; using System.CodeDom.Compiler; using Microsoft.CSharp; class DynamicCompile { public static void Main() { string sourceCode = @" using System; class HelloWorld { static void Main(string[] args){ Console.WriteLine(""Hello from dynamically compiled code!""); } }"; var parameters = new CompilerParameters(); parameters.GenerateExecutable = true; // 设置为true则生exe, false则是dll parameters.OutputAssembly = @"D:\Temp\DynamicHelloWorld.exe"; CSharpCodeProvider provider = new CSharpCodeProvider(); CompilerResults results = provider.CompileAssemblyFromSource(parameters, sourceCode); if (results.Errors.HasErrors) { foreach(var error in results.Errors){ Console.WriteLine(error.ToString()); } } else { Console.WriteLine("Compilation succeeded."); } } } ``` 这段代码展示了怎样使用 `Microsoft.CSharp.CSharpCodeProvider` 和 `System.CodeDom.Compiler.CompilerParameters` 结合起来实现在运行期间根据给定的源代码文本构造新的可执行文件的功能[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值