RSS

Advent LINQ (18) : Expressionの探索

18 12月

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="() =&gt; (&quot;ABCDEFG&quot;.StartsWith(&quot;ABC&quot;) == True)">
	<Equal value="(&quot;ABCDEFG&quot;.StartsWith(&quot;ABC&quot;) == True)">
		<Call value="&quot;ABCDEFG&quot;.StartsWith(&quot;ABC&quot;)">
			<Constant value="&quot;ABCDEFG&quot;" />
			<Constant value="&quot;ABC&quot;" />
		</Call>
		<Constant value="True" />
	</Equal>
</Lambda>

ExpressionVisitorクラスには、「VisitBinary」のような細分化されたメソッドが用意されている。これらのノード別メソッドはかなりの種類があるため、すべてを細かく制御するのは骨が折れる。だが、正しく制御すれば、Expressionをより良いXML形式に変換できるだろう。

広告
 
コメントする

投稿者: : 2013/12/18 投稿先 .NET, LINQ

 

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Google フォト

Google アカウントを使ってコメントしています。 ログアウト /  変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中

 
%d人のブロガーが「いいね」をつけました。