実行時に「進化」する業務アプリ:CodeDomProviderによる動的スクリプトエンジンの構築術
業務アプリケーションを開発していると、必ず突き当たる壁がある。それは「ユーザーが定義する計算式の柔軟性」だ。
「Excelのように、現場の担当者が自分で計算式を入力できるようにしてほしい」。この要望を安易に受け入れ、`DataTable.Compute` を多用したり、自前でパーサーを書いて再帰処理でスタックオーバーフローを起こしたりしているなら、今日でその設計は卒業すべきだ。
真の業務自動化エンジニアは、動的なロジックを「解釈」させない。「コンパイル」させるのだ。 今回は、VB.NETが持つ強力な武器、`CodeDomProvider` を使った高パフォーマンスなスクリプトエンジンの実装手法を伝授する。
—
なぜ「インタープリタ」ではなく「コンパイラ」なのか
多くのエンジニアが陥る罠は、条件式を文字列解析(パース)して一つずつ評価する「インタープリタ」的なアプローチだ。しかし、この手法は以下の点で業務アプリとして失格である。
1. パフォーマンスの欠如: 複雑な計算をループの中で繰り返せば、解析コストがボトルネックになる。
2. メンテナンス不能: 構文規則が複雑になればなるほど、デバッグは迷宮入りする。
3. 型安全性の欠如: 実行時エラーが多発し、堅牢性が担保できない。
`CodeDomProvider` を使えば、ユーザーの入力した文字列をその場でIL(中間言語)にコンパイルし、メモリ上にアセンブリとしてロードできる。実行時はネイティブコードに近い速度で動作し、コンパイル時に型チェックも行われる。これがプロの選ぶ道だ。
—
堅牢なスクリプトエンジンの実装コード
以下のコードは、入力された計算式を動的にコンパイルし、デリゲートとして呼び出すための最小構成かつ最強のパターンだ。
Imports System.CodeDom.Compiler
Imports System.Reflection
Imports Microsoft.VisualBasic
Public Class DynamicScriptEngine
”’
”’
Public Shared Function CreateCalculator(formula As String) As Func(Of Double, Double)
Dim provider As New VBCodeProvider()
Dim cp As New CompilerParameters()
‘ メモリ上で完結させる(一時ファイルを作らない)
cp.GenerateInMemory = True
cp.GenerateExecutable = False
cp.ReferencedAssemblies.Add(“System.dll”)
‘ 動的コードの組み立て(外部からの注入を考慮し、メソッドとしてラップ)
Dim code As String = $@”
Public Class CompiledCalculator
Public Function Calculate(x As Double) As Double
Return {formula}
End Function
End Class”
Dim results As CompilerResults = provider.CompileAssemblyFromSource(cp, code)
If results.Errors.HasErrors Then
Throw New Exception(“式に構文エラーがあります: ” & results.Errors(0).ErrorText)
End If
‘ リフレクションで生成されたインスタンスを取得
Dim assembly As Assembly = results.CompiledAssembly
Dim type As Type = assembly.GetType(“CompiledCalculator”)
Dim obj As Object = Activator.CreateInstance(type)
‘ 型安全なデリゲートとして返却
Return DirectCast(Delegate.CreateDelegate(GetType(Func(Of Double, Double)), obj, “Calculate”), Func(Of Double, Double))
End Function
End Class
—
運用上の注意:セキュリティとライフサイクル
この手法を採用する際、必ず心に刻んでおくべき「3つの鉄則」がある。
1. サニタイズは必須
上記のコードは `formula` を文字列連結している。そのままユーザー入力を渡すと、悪意のあるコード(`System.IO.File.Delete(“C:\…”)` など)が実行される可能性がある。業務アプリとして提供する場合は、必ずホワイトリスト方式で演算子や関数を制限し、入力値を厳密にチェックすること。
2. アセンブリのアンロード問題
`GenerateInMemory = True` を使っても、一度ロードされたアセンブリは `AppDomain` が終了するまでメモリに残り続ける。頻繁に式を変更してコンパイルを繰り返すと、メモリリーク(メモリ肥大化)を起こす。
- 対策: 計算式が変わらない限りは、一度生成したデリゲートを `Dictionary(Of String, Func(Of Double, Double))` にキャッシュし、使い回すこと。
3. データベースとの連携
DBから計算式を読み込む際、テーブルには「式」だけでなく「バージョン番号」を持たせておくべきだ。計算ロジックが変更された際、過去のデータの再計算が正しく行えるようにするためである。
—
結びに:エンジニアとしての矜持
「動的コンパイル」は魔法ではない。しかし、VB.NETという枯れた、かつ強力な言語仕様を深く理解していれば、それは最強の武器になる。
業務アプリにおいて、コードは「書く」ものではなく、ユーザーの要求に応じて「生成される」ものへと進化しつつある。この設計を取り入れることで、あなたの作るツールは、単なる「入力画面」から、現場の知恵を高速に実行する「インテリジェントなプラットフォーム」へと昇華するだろう。
さあ、コードを書いてくれ。現場の生産性を極限まで引き上げる準備は、もう整っているはずだ。
