KiMoGiGi 技术文集

不在乎选择什么,而在乎坚持多久……

IT博客 首页 联系 聚合 管理
  185 Posts :: 14 Stories :: 48 Comments :: 0 Trackbacks
1、Previously

【原】实现表达式的计算 中,用Stack解决了问题。

但单单对于解决此问题的话,直接用.net的Emit,简单的一些API就可以解决计算表达式的问题。

2、Logic

逻辑很简单,传入表达式并且只有+、-、×、/、(、)的话,写代码表达式其实就可以直接计算出结果。

如:字符串表达式“1+4+7+(6*2*(4+3)+6-5)+2”。

假设我们对应生成一个方法,如下

public decimal GetResult()
{
    
return 1+4+7+(6*2*(4+3)+6-5)+2;
}

获得的返回值就是计算结果。那么问题就简化成,怎么把代码字符串动态编译成assembly,从而用反射调用。

3、Implement

首先我们先定义一个接口:
public interface IMock 
    {
        
decimal Go();
    }

在正常情况,我们使用反射调用一个方法的时候,会写类似以下的代码:

MethodInfo methodInfo = mockInstance.GetType().GetMethod("Go");
            
return Convert.ToDecimal(methodInfo.Invoke(mockInstance, null));

由于Invoke速度较慢,这个接口的作用在于,可以避免使用反射调用方法,从而加快速度。

以下是具体实现功能的类:
    public class EmitCalculate
    {
        
/// <summary>
        
/// 缓存表达式与计算实例
        
/// </summary>
        
/// <remarks>可避免对应相同的表达式重复动态编译</remarks>
        private static Dictionary<string, IMock> cacheInstances = new Dictionary<string, IMock>();

        
public static decimal Calculate(string expression)
        {
            IMock mockInstance 
= null;

            
if (!cacheInstances.ContainsKey(expression))
            {
                
//计算编译耗费的时间
                Stopwatch sw = Stopwatch.StartNew();

                CSharpCodeProvider csProvider 
= new CSharpCodeProvider();

                CompilerParameters paras 
= new CompilerParameters();
                paras.GenerateExecutable 
= false;
                paras.GenerateInMemory 
= true;
                paras.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location);

                
string code = @"
public class Mock:ExpressionCalculate.IMock
{
    public decimal Go()
    {
        return #code#;
    }
}
";
                
//编译代码。   
                CompilerResults result = csProvider.CompileAssemblyFromSource(paras, code.Replace("#code#", expression));

                
//获取编译后的程序集,并获取实例存放到缓存中
                cacheInstances.Add(expression, (IMock)result.CompiledAssembly.CreateInstance("Mock"));

                sw.Stop();
                Console.WriteLine(
"Complier Time:{0}", sw.ElapsedMilliseconds);
            }

            mockInstance 
= cacheInstances[expression];

            
return mockInstance.Go();
        }
    }

根据表达式,动态构造Mock类,当然Mock类需要继承IMock。简单清楚就可以返回表达式的计算值。

4、Full View

using System;
using System.Collections.Generic;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;
using System.Diagnostics;

namespace ExpressionCalculate
{
    
public interface IMock 
    {
        
decimal Go();
    }

    
public class EmitCalculate
    {
        
/// <summary>
        
/// 缓存表达式与计算实例
        
/// </summary>
        
/// <remarks>可避免对应相同的表达式重复动态编译</remarks>
        private static Dictionary<string, IMock> cacheInstances = new Dictionary<string, IMock>();

        
public static decimal Calculate(string expression)
        {
            IMock mockInstance 
= null;

            
if (!cacheInstances.ContainsKey(expression))
            {
                
//计算编译耗费的时间
                Stopwatch sw = Stopwatch.StartNew();

                CSharpCodeProvider csProvider 
= new CSharpCodeProvider();

                CompilerParameters paras 
= new CompilerParameters();
                paras.GenerateExecutable 
= false;
                paras.GenerateInMemory 
= true;
                paras.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location);

                
string code = @"
public class Mock:ExpressionCalculate.IMock
{
    public decimal Go()
    {
        return #code#;
    }
}
";
                
//编译代码。   
                CompilerResults result = csProvider.CompileAssemblyFromSource(paras, code.Replace("#code#", expression));

                
//获取编译后的程序集,并获取实例存放到缓存中
                cacheInstances.Add(expression, (IMock)result.CompiledAssembly.CreateInstance("Mock"));

                sw.Stop();
                Console.WriteLine(
"Complier Time:{0}", sw.ElapsedMilliseconds);
            }

            mockInstance 
= cacheInstances[expression];

            
return mockInstance.Go();
        }
    }
}

调用:
string expression = "1+4+7+(6*2*(4+3)+6-5)+2";
EmitCalculate.Calculate(expression);

5、Summary

这种方法耗费比较多的时间在在编译上,编译后执行使用了接口类型,没有用反射调用方法,速度也是很快的。


posted on 2009-02-20 22:17 KiMoGiGi 阅读(347) 评论(0)  编辑 收藏 引用 所属分类: Basic
只有注册用户登录后才能发表评论。