DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

文字列の計算式の計算結果を取得する

ここでは、evalのように、例えば"(1+6)*5/(7-4)"のような計算式を表す文字列から、その計算結果を取得する方法を紹介します。

サードパーティーのライブラリを使用する

C#やVB.NETには、evalやそれに相当する機能がありません。よって、自分で計算式を解析するコードを書くか、サードパーティーのライブラリを利用するか、あるいは何らかの工夫するかといった方法しかありません。

もしサードパーティーのライブラリを利用できるのであれば、それが一番良いと思います。以下に無料で利用できるライブラリを幾つか紹介します。

自分で解析する方法

自分で計算式を解析し、計算するコードを書くならば、上記で紹介したページが参考になりますが、それ以外に参考になりそうなページを以下に紹介します。

JScript.NETのEvalを使用する方法

これは、GotDotNet Message Boardsの「String to Numeric」(リンク切れ)のスレッドでenderminhさんが紹介されていた方法です。

この方法によると、まず参照に「Microsoft.Jscript」と「Microsoft.Vsa」を追加する必要があります。そうすれば、例えば、次のようなコードで計算ができます。

VB.NET
コードを隠すコードを選択
'計算式
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)
C#
コードを隠すコードを選択
//計算式
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)」で紹介されています。

この方法によると、次のようなコードが書けます。

VB.NET
コードを隠すコードを選択
'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)
C#
コードを隠すコードを選択
//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を使用する方法

さらに、掲示板でピラルクさんが提案されたように、CSharpCodeProviderを使って、計算式が含まれるコードの文字列をコンパイルし、実行する方法もあります。この方法は、「Runtime Compilation (A .NET eval statement)」や「Evaluating Mathematical Expressions by Compiling C# Code at Runtime」などでも紹介されています。

具体的なコードは、例えば、次のようなものです。

VB.NET
コードを隠すコードを選択
'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)
C#
コードを隠すコードを選択
//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を使用する方法

Microsoft Script Controlが使用できるならば、VBScriptやJSCriptのEval関数を使う方法もあります。

VB.NET
コードを隠すコードを選択
'計算式
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)
C#
コードを隠すコードを選択
//計算式
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メソッドを使用する方法

DataTable.Computeメソッドを使うと、次のようにして計算できます。

VB.NET
コードを隠すコードを選択
'計算式
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)
C#
コードを隠すコードを選択
//計算式
string exp = "(1+6)*5/(7-4)";

//式を計算する
System.Data.DataTable dt = new System.Data.DataTable();
double result = (double)dt.Compute(exp, "");

//結果を表示
Console.WriteLine(result);

ここで紹介した以外にも、まだまだ方法がありそうですが、きりが無いので、この辺で終わりにします。面白い方法をご存知でしたら、ぜひ教えて下さい。

  • 履歴:
  • 2013/6/30 自分で解析する方法にリンクを追加。リンク切れを修正。DataTable.Computeメソッドを使った方法のコードを追加。
  • 2016/1/18 「自分で解析する方法」から「サードパーティーのライブラリを使用する」を分離して書き直したなど。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • コードの先頭に記述されている「Imports ??? がソースファイルの一番上に書かれているものとする」(C#では、「using ???; がソースファイルの一番上に書かれているものとする」)の意味が分からないという方は、こちらをご覧ください。
  • 「???を参照に追加します」の意味が分からないという方は、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。