連載:C# 3.0入門

第2回 ラムダ式と型推論

株式会社ピーデー 川俣 晶
2008/05/16
Page1 Page2 Page3

オーバーロードの解決

 ラムダ式はオーバーロードの解決にも影響する。難しい話ではないが、理解していないと引っ掛かることもあると思うので、簡単なサンプル・コードを掲載しておく。

 以下のリストは、int型を返すラムダ式を、double型を返すデリゲート型を引数に受け取るメソッドに渡す例である。整数から実数への暗黙の変換は存在するので、このコードは問題なく動作する。

using System;

class Program
{
  static void Output(Func<double> f)
  {
    Console.WriteLine("Output by double: {0}",f());
  }

  static void Main(string[] args)
  {
    Output(() => 123); // 出力:Output by double: 123
  }
}
リスト9 暗黙変換により適合するラムダ式

 このプログラムに同じ名前のメソッドを追加してみよう。ただし、こちらではint型を返すデリゲート型を引数に持つ。

using System;

class Program
{
  static void Output(Func<double> f)
  {
    Console.WriteLine("Output by double: {0}",f());
  }

  // 新規追加メソッド
  static void Output(Func<int> f)
  {
    Console.WriteLine("Output by int: {0}", f());
  }

  static void Main(string[] args)
  {
    Output(() => 123); // 出力:Output by int: 123
  }
}
リスト10 メソッドの追加によりオーバーロードの解決が変化

 見てのとおり、呼び出しのラムダ式は一切変更していないが、別のメソッドを呼び出すようになった。これは、ラムダ式がオーバーロードの解決に影響を与えているためである。ラムダ式が持つ型が、オーバーロードの解決メカニズムに影響を与え、より適したメソッドを選択させたのである。そのため、より適した型を持つメソッドが追加されると、それによって別のメソッドが呼び出されるようになったわけである。

匿名メソッドとラムダ式の違いと式ツリー

 「式ツリー」は、通常のプログラムで使用する可能性が極めて低い機能であるため、ここは読み飛ばしても構わない。式を計算するのではなく、式を加工するという話題がピンと来る読者だけ読めばよいだろう。

 さて、ラムダ式は単なる匿名メソッドの簡略表記ではない。すでにいくつかの相違点を説明したが、最大の相違は以下の点だろう。

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

 式ツリーとは.NET Framework 3.5の新機能であり、実行中に式をオブジェクトのツリーとして構築し、コンパイルと実行を可能にするものである。すでにコンパイルされ、実行中のプログラムが、実行中の環境下でさらに式を構築してコンパイルして実行できるわけである。

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

 さて、実際に実行中に式を加工する例を以下に示す。

 このプログラムは、「x => x + 1」というラムダ式を実行中に加工して、「x => x - 1」という式に作り直してコンパイル、計算を行う。なお、式ツリーを解説することはこの連載の主テーマではないので、概要だけ説明する。

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

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

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

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

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

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

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

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

public class AddToSubModefier : 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));

    // 式の中の加算を減算に置換する
    AddToSubModefier treeModifier = new AddToSubModefier();
    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));
  }
}
リスト11 ラムダ式を式ツリーに代入して加工

次回予告

 ラムダ式は、ラムダ式単体で使っても極めて強力だが、これはLinqで簡潔にクエリ式を書くために必須の機能と考えた方がよいのかもしれない。そう考えれば、この連載が提供する「価値」はまだまだ序の口ということになる(Linqについては次回以降で詳しく取り上げる)。

 それとは別に、今回、式ツリーについて触れることができたことも、予期せざる成果といえる。本来の執筆予定ではこの連載で式ツリーに触れるはずではなかったが、匿名メソッドとラムダ式の相違点の1つということで、簡単にサンプル・コードを1つ書いた。このような機会がなければ実際に使う可能性もなかった(つまり、普通のプログラムではまず使わない)機能だが、何か新しい応用が生まれそうなワクワク感があるのも事実である。

 さて、次回は自動実装と自動定義をテーマとして、匿名型や自動実装プロパティなどについて取り上げたいと考えている。請うご期待。End of Article

 

 INDEX
  C# 3.0入門
  第2回 ラムダ式と型推論
    1.ラムダ式を使用した事例
    2.使える既存のデリゲート/ジェネリック・メソッドと型推論
  3.オーバーロードの解決/匿名メソッドとラムダ式の違いと式ツリー

インデックス・ページヘ  「C# 3.0入門」


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 記事ランキング

本日 月間