Expressionは、代入されたラムダ式の構造によっていくらでも変化する。そのため、例で見せたように、あらかじめどの種類のExpressionがやってくるかを予想する事は難しい。そういった構造を解析する方法はいくつか考えられる。単に一つ一つの構造をswitch文のような分岐で処理する事もできるが、標準的な方法として「ExpressionVisitor」クラスが用意されている。
ExpressionVisitorクラスは、Visitorパターンを使用してExpressionを解析する。例えばMethodCallExpressionは、インスタンスを示す「Object」、対象のメンバを示す「Method」、引数群を示す「Arguments」が存在するが、これらもまたExpressionであるので、再帰的に呼び出される。このクラスを継承して、関心のある種類のExpressionのメソッドをオーバーライドしてチェックできる。
特に再帰の入り口となるのが「Visit」メソッドだ。このメソッドの基底の実装が、細分化された「Visit~」メソッドをコールする。試しにVisitをオーバーライドしたのが、以下の例だ。
// ExpressionVisitorクラスを継承する public sealed class FlatDumpExpressionVisitor : ExpressionVisitor { public FlatDumpExpressionVisitor() { } // 再起処理の入り口となるメソッド public override Expression Visit(Expression node) { // 引数で渡されたExpressionをダンプする Debug.WriteLine("{0}: {1}", node.NodeType, node.ToString()); // デフォルトの実装を呼び出す(細分化されたVisitメソッドを呼び分け、再帰処理する) return base.Visit(node); } }
これを実行すると、以下のような出力が得られる。
// ダンプ対象の式を定義 Expression<Func<bool>> func12 = () => "ABCDEFG".StartsWith("ABC") == true; // ダンプを実行 var visitor = new FlatDumpExpressionVisitor(); visitor.Visit(func12); // Lambda: () => ("ABCDEFG".StartsWith("ABC") == True) // Equal: ("ABCDEFG".StartsWith("ABC") == True) // Call: "ABCDEFG".StartsWith("ABC") // Constant: "ABCDEFG" // Constant: "ABC" // Constant: True
ツリーの階層構造は明確ではないが、ラムダ式から始まって、式の細部を探索していることが読み取れる。また、このFlatDumpExpressionVisitorによって、すべての式の要素がダンプされていそうだ。式中に与えたStartsWithの呼び出しや、固定的な文字列、右辺の「true」も列挙されている。
もう少し、何とかしたい。そこで、以下のようなコードを書いてみる。
public sealed class FlatDumpExpressionVisitor : ExpressionVisitor { private readonly Stack<XElement> elementStack_ = new Stack<XElement>(); public FlatDumpExpressionVisitor() { } public XElement CurrentElement { get; private set; } public override Expression Visit(Expression node) { elementStack_.Push(this.CurrentElement); var element = new XElement(node.NodeType.ToString()); element.Add(new XAttribute("value", node.ToString())); if (this.CurrentElement != null) { this.CurrentElement.Add(element); } this.CurrentElement = element; var result = base.Visit(node); this.CurrentElement = elementStack_.Pop() ?? this.CurrentElement; return result; } }
これは、再帰的にVisitが呼び出されたときに、XElementを使って階層構造を組み立てるExpressionVisitorの実装だ。まだ式の出力(value属性)が読みにくいが、どのような階層構造になっているのかは把握できる。
<Lambda value="() => ("ABCDEFG".StartsWith("ABC") == True)"> <Equal value="("ABCDEFG".StartsWith("ABC") == True)"> <Call value=""ABCDEFG".StartsWith("ABC")"> <Constant value=""ABCDEFG"" /> <Constant value=""ABC"" /> </Call> <Constant value="True" /> </Equal> </Lambda>
ExpressionVisitorクラスには、「VisitBinary」のような細分化されたメソッドが用意されている。これらのノード別メソッドはかなりの種類があるため、すべてを細かく制御するのは骨が折れる。だが、正しく制御すれば、Expressionをより良いXML形式に変換できるだろう。