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形式に変換できるだろう。