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