ここでは、evalのように、例えば"(1+6)*5/(7-4)"のような計算式を表す文字列から、その計算結果を取得する方法を紹介します。
C#やVB.NETには、evalやそれに相当する機能がありません。よって、自分で計算式を解析するコードを書くか、サードパーティーのライブラリを利用するか、あるいは何らかの工夫するかといった方法しかありません。
もしサードパーティーのライブラリを利用できるのであれば、それが一番良いと思います。以下に無料で利用できるライブラリを幾つか紹介します。
自分で計算式を解析し、計算するコードを書くならば、上記で紹介したページが参考になりますが、それ以外に参考になりそうなページを以下に紹介します。
これは、GotDotNet Message Boardsの「String to Numeric」(リンク切れ)のスレッドでenderminhさんが紹介されていた方法です。
この方法によると、まず参照に「Microsoft.Jscript」と「Microsoft.Vsa」を追加する必要があります。そうすれば、例えば、次のようなコードで計算ができます。
'計算式 Dim exp As String = "(1+6)*5/(7-4)" Dim ve As Microsoft.JScript.Vsa.VsaEngine = _ Microsoft.JScript.Vsa.VsaEngine.CreateEngine() Dim result As Double = _ CDbl(Microsoft.JScript.Eval.JScriptEvaluate(exp, ve)) '結果を表示 Console.WriteLine(result)
//計算式 string exp = "(1+6)*5/(7-4)"; Microsoft.JScript.Vsa.VsaEngine ve = Microsoft.JScript.Vsa.VsaEngine.CreateEngine(); double result = (double) Microsoft.JScript.Eval.JScriptEvaluate( exp, ve); //結果を表示 Console.WriteLine(result);
また、JavaScriptをjsc.exeでコンパイルしてライブラリを作成し、それを使用する方法が、「Evaluate Expressions from C# using JavaScript's Eval() Function」などで紹介されています。
さらに、JScriptCodeProviderでEvalメソッドを実行する方法が、「An Eval Function for C# using JScript.NET (JavaScript)」で紹介されています。
この方法によると、次のようなコードが書けます。
'Imports System.Reflection 'Imports System.CodeDom.Compiler 'がソースファイルの一番上に書かれているものとする '計算式 Dim exp As String = "(1+6)*5/(7-4)" '計算するためのコード Dim [source] As String = _ "package Evaluator {" + vbCrLf + _ "class Evaluator {" + vbCrLf + _ "public function Eval(expr : String) : String {" + vbCrLf + _ "return eval(expr);}}}" 'コンパイルするための準備 Dim cp = New Microsoft.JScript.JScriptCodeProvider Dim icc As ICodeCompiler = cp.CreateCompiler() Dim cps As New CompilerParameters Dim cres As CompilerResults 'メモリ内で出力を生成する cps.GenerateInMemory = True 'コンパイルする cres = icc.CompileAssemblyFromSource(cps, [source]) 'コンパイルしたアセンブリを取得 Dim asm As [Assembly] = cres.CompiledAssembly 'クラスのTypeを取得 Dim t As Type = asm.GetType("Evaluator.Evaluator") 'インスタンスの作成 Dim eval As Object = Activator.CreateInstance(t) 'Evalメソッドを実行し、結果を取得 Dim result As String = CStr(t.InvokeMember("Eval", _ BindingFlags.InvokeMethod, Nothing, eval, New Object() {exp})) '結果を表示 Console.WriteLine(result)
//using System.Reflection; //using System.CodeDom.Compiler; //がソースファイルの一番上に書かれているものとする //計算式 string exp = "(1+6)*5/(7-4)"; //計算するためのコード string source = @"package Evaluator { class Evaluator { public function Eval(expr : String) : String { return eval(expr); } } }"; //コンパイルするための準備 CodeDomProvider cp = new Microsoft.JScript.JScriptCodeProvider(); ICodeCompiler icc = cp.CreateCompiler(); CompilerParameters cps = new CompilerParameters(); CompilerResults cres; //メモリ内で出力を生成する cps.GenerateInMemory = true; //コンパイルする cres = icc.CompileAssemblyFromSource(cps, source); //コンパイルしたアセンブリを取得 Assembly asm = cres.CompiledAssembly; //クラスのTypeを取得 Type t = asm.GetType("Evaluator.Evaluator"); //インスタンスの作成 object eval = Activator.CreateInstance(t); //Evalメソッドを実行し、結果を取得 string result = (string) t.InvokeMember("Eval", BindingFlags.InvokeMethod, null, eval, new object[] {exp}); //結果を表示 Console.WriteLine(result);
さらに、掲示板でピラルクさんが提案されたように、CSharpCodeProviderを使って、計算式が含まれるコードの文字列をコンパイルし、実行する方法もあります。この方法は、「Runtime Compilation (A .NET eval statement)」や「Evaluating Mathematical Expressions by Compiling C# Code at Runtime」などでも紹介されています。
具体的なコードは、例えば、次のようなものです。
'Imports System.Reflection 'Imports System.CodeDom.Compiler 'がソースファイルの一番上に書かれているものとする '計算するためのコード Dim [source] As String = _ "public class MainClass {" + vbCrLf + _ "public static double EVal() {" + vbCrLf + _ "return (1d+6d)*5d/(7d-4d);" + vbCrLf + _ "}}" 'コンパイルするための準備 Dim cp = New Microsoft.CSharp.CSharpCodeProvider Dim icc As ICodeCompiler = cp.CreateCompiler() Dim cps As New CompilerParameters Dim cres As CompilerResults 'メモリ内で出力を生成する cps.GenerateInMemory = True 'コンパイルする cres = icc.CompileAssemblyFromSource(cps, [source]) 'コンパイルしたアセンブリを取得 Dim asm As [Assembly] = cres.CompiledAssembly 'MainClassクラスのTypeを取得 Dim t As Type = asm.GetType("MainClass") 'EValメソッドを実行し、結果を取得 Dim d As Double = CDbl(t.InvokeMember("EVal", _ BindingFlags.InvokeMethod, Nothing, Nothing, Nothing)) '結果を表示 Console.WriteLine(d)
//using System.CodeDom.Compiler; //using System.Reflection; //がソースファイルの一番上に書かれているものとする //計算するためのコード string source = @" public class MainClass { public static double EVal() { return (1d+6d)*5d/(7d-4d); } }"; //コンパイルするための準備 CodeDomProvider cp = new Microsoft.CSharp.CSharpCodeProvider(); ICodeCompiler icc = cp.CreateCompiler(); CompilerParameters cps = new CompilerParameters(); CompilerResults cres; //メモリ内で出力を生成する cps.GenerateInMemory = true; //コンパイルする cres = icc.CompileAssemblyFromSource(cps, source); //コンパイルしたアセンブリを取得 Assembly asm = cres.CompiledAssembly; //MainClassクラスのTypeを取得 Type t = asm.GetType("MainClass"); //EValメソッドを実行し、結果を取得 double d = (double) t.InvokeMember("EVal", BindingFlags.InvokeMethod, null, null, null); //結果を表示 Console.WriteLine(d);
この方法は計算式が変わるたびにコンパイルが必要になり、メモリのアセンブリをどのように解放するかという問題があります(この問題は「Dynamically executing code in .Net」に詳しい)。そのため、実用としては困難かもしれません。
Microsoft Script Controlが使用できるならば、VBScriptやJSCriptのEval関数を使う方法もあります。
'計算式 Dim exp As String = "(1+6)*5/(7-4)" Dim t As Type = _ Type.GetTypeFromProgID("MSScriptControl.ScriptControl") Dim obj As Object = Activator.CreateInstance(t) t.InvokeMember("Language", _ System.Reflection.BindingFlags.SetProperty, _ Nothing, _ obj, _ New Object() {"vbscript"}) 'Eval関数で計算を実行して結果を取得 Dim result As Double = CDbl( _ t.InvokeMember("Eval", _ System.Reflection.BindingFlags.InvokeMethod, _ Nothing, _ obj, _ New Object() {exp})) '結果を表示 Console.WriteLine(result)
//計算式 string exp = "(1+6)*5/(7-4)"; Type t = Type.GetTypeFromProgID("MSScriptControl.ScriptControl"); object obj = Activator.CreateInstance(t); t.InvokeMember("Language", System.Reflection.BindingFlags.SetProperty, null, obj, new object[] {"vbscript"}); //Eval関数で計算を実行して結果を取得 double result = (double) t.InvokeMember("Eval", System.Reflection.BindingFlags.InvokeMethod, null, obj, new object[] {exp}); //結果を表示 Console.WriteLine(result);
DataTable.Computeメソッドを使うと、次のようにして計算できます。
'計算式 Dim exp As String = "(1+6)*5/(7-4)" '式を計算する Dim dt As New System.Data.DataTable() Dim result As Double = CDbl(dt.Compute(exp, "")) '結果を表示 Console.WriteLine(result)
//計算式 string exp = "(1+6)*5/(7-4)"; //式を計算する System.Data.DataTable dt = new System.Data.DataTable(); double result = (double)dt.Compute(exp, ""); //結果を表示 Console.WriteLine(result);
ここで紹介した以外にも、まだまだ方法がありそうですが、きりが無いので、この辺で終わりにします。面白い方法をご存知でしたら、ぜひ教えて下さい。
(この記事は「.NETプログラミング研究 第34号」で紹介したものです。)