連載:[完全版]究極のC#プログラミング

Appendix 2 .NET Framework 3.5の新機能 − 式ツリー

川俣 晶
2010/06/21
Page1 Page2

 「式ツリー」は、LINQ to SQLなどでは必須の機能だが、通常のプログラムで使用する可能性はきわめて低い機能でもある。この付録は、式を計算するのではなく式を加工するという話題がピンとくる読者のために書かれているが、LINQ to SQLを極めたい読者も読んでおくとよいだろう。なお、式ツリーを解説することはこの本書の主テーマではないので、概要だけ説明する。

 さて、ラムダ式は単なる匿名メソッドの簡略表記ではない。最大の相違は次の点だろう。

  • 式本体を持つラムダ式は、式ツリーに変換できる

 式ツリーとは.NET Framework 3.5の新機能であり、実行中に式をオブジェクトのツリーとして構築し、コンパイルと実行を可能にするものである。LINQ to SQLでは式ツリーをSQL文に翻訳してクエリを行うために使用されているがそれだけではない。すでにコンパイルされ、実行中のプログラムが、実行中の環境下でさらに式を構築してコンパイルして実行できるわけである。

 これにより、実行中に判明する条件に対して最適化した式を作成し、実行速度を向上させたり、式そのものを演算の対象とした処理を記述したりすることができる。前者はループ最内周で行う演算など、条件判断すら取り除いて最適化した式だけを計算したい場合などが考えられる。後者ならば、たとえば、式を微分するという使用例を見たことがある(方程式を数値的に微分するのではなく、式そのものを変形させて微分された式を得るということ)。

 さて、実際に実行中に式を加工する例をリストA2.1に示す。

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq.Expressions;

public abstract class ExpressionVisitor
{
  (「.NET Framework 開発者ガイド 方法 : 式ツリー ビジタを実装する」に掲載されているExpressionVisitorクラスのコードをここに挿入する)
}

public class AddToSubModifier : ExpressionVisitor
{
  public Expression Modify(Expression expression)
  {
    return Visit(expression);
  }

  protected override Expression VisitBinary(BinaryExpression b)
  {
    if (b.NodeType == ExpressionType.Add)
    {
      Expression left = this.Visit(b.Left);
      Expression right = this.Visit(b.Right);

      return Expression.MakeBinary(
            ExpressionType.Subtract, left, right,
            b.IsLiftedToNull, b.Method);
    }
    return base.VisitBinary(b);
  }
}

class Program
{
  static void Main(string[] args)
  {
    // 1を加算するラムダ式から式オブジェクトを作成する
    Expression<Func<int, int>> add1Expr = x => x + 1;
    Console.WriteLine(add1Expr);

    // 式オブジェクトをコンパイルしてデリゲートを得る
    Func<int, int> add1Delegate = add1Expr.Compile();

    // デリゲートを呼び出して加算を実行
    Console.WriteLine(add1Delegate(1));

    // 式の中の加算を減算に置換する
    AddToSubModifier treeModifier = new AddToSubModifier();
    Expression<Func<int, int>> subtract1Expr
        = (Expression<Func<int, int>>)
            treeModifier.Modify((Expression)add1Expr);
    Console.WriteLine(subtract1Expr);

    // 式オブジェクトをコンパイルしてデリゲートを得る
    Func<int, int> subtract1Delegate = subtract1Expr.Compile();

    // デリゲートを呼び出して減算を実行
    Console.WriteLine(subtract1Delegate(1));
  }
}
リストA2.1 ラムダ式を式ツリーに代入して加工

 このプログラムは、「x => x + 1」というラムダ式を実行中に加工して、「x => x - 1」という式に作り直してコンパイル、計算を行う。

 式ツリーは不変であるため、式ツリーの加工は、ビジターパターン(ツリーの全体を順次訪問する)により、ツリー全体を複製しながら部分を改変することで行う。式ツリーを訪問するビジターパターンは、次の「.NET Framework開発者ガイド 方法:式ツリービジタを実装する」で実装されたコードが公開されているので、これを使えばよいだろう。

.NET Framework開発者ガイド 方法:式ツリービジタを実装する
http://msdn.microsoft.com/ja-jp/library/bb882521.aspx

 この例では、加算を行うExpressionType.Add型のノード(オーバーフローチェックを行わない足し算)を発見した場合、ExpressionType.Subtract型のノード(アンダーフローチェックを行わない引き算)を作成して置換するようになっている。

 Mainメソッドに埋め込んだコメントを見れば、全体的な動作の流れはわかると思う。また、AddToSubModifierは式ツリーを理解しないと読み取れないと思うので、読み飛ばしてかまわない。

 ここで注目すべき行は、次のただ1つ。

Expression<Func<int, int>> add1Expr = x => x + 1;

 と、デリゲート型変数ではなく、式を示すExpression型の変数にラムダ式を代入できるという点のみである。


 INDEX
  [完全版]究極のC#プログラミング
  Appendixes コンパイラ新機能/式ツリー
    1.Appendix 1 Visual Studio 2005/2008のコンパイラの新機能
  2.Appendix 2 .NET Framework 3.5の新機能 − 式ツリー
 
インデックス・ページヘ  「[完全版]究極のC#プログラミング」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間