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

链接: Evaluating Mathematical Expressions by Compiling C# Code at Runtime, by Marcin Cuprjak (aka Vlad Tepes)

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

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

假如说我们要计算一个函数的值:
z = f(x, y)

但是我们在编译时还不知道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).");
            }
        }
    }
}


试验一下程序的运行:
引用
Enter a math expression, with 2 parameters available (x and y):
x + Math.Sin(y)
Enter x: 1
Enter y: 2
The result is: 1.90929742682568

也验证一下出错时会怎样:
引用
Enter a math expression, with 2 parameters available (x and y):
(x + 1)) / y
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).

嘛,我是在简体中文的Windows XP上运行这程序,所以错误提示里出现了中文……


Anyway,作为桌面计算器上面的代码还是太简陋了。本文只是要做个使用System.CodeDom的例子。
在上面的使用场景中,由于我们无法在运行前得知函数的定义,所以有些错误带到了运行时才能发现也不算是坏事。但假如我们使用System.CodeDom生成代码前就能知道完整的源代码是怎样的,那么让源代码放在字符串里显然不够好——类型安全消失了,IDE等开发工具的支持也没了,因为我们所有的待编译代码都在字符串里。
如果有办法既能在运行时才编译代码,又能在编译时就发现语法错误,那就……请期待下一篇文~

你可能感兴趣的:(C++,c,XP,C#,ide)