C Sharpの基礎 - ラムダ式

提供:MochiuWiki - SUSE, Electronic Circuit, PCB
2021年11月18日 (木) 09:26時点におけるWiki (トーク | 投稿記録)による版 (文字列「source lang」を「syntaxhighlight lang」に置換)
ナビゲーションに移動 検索に移動

概要

ラムダ式とは、関数(メソッド)を整数等の変数と全く同列に扱う手法のことである。

  • デリゲートに対して代入すると、匿名メソッドと同じ扱いになる。
    ラムダ式は、匿名メソッドと比較して、より簡潔に記述することができる。
  • Expression型の変数に代入すると、式木データになる。



ラムダ式

匿名関数

ラムダ式の特徴を以下に示す。

  • 匿名関数を使用することにより、インラインでデリゲートの定義を記述し、デリゲートを作成できる。
  • 匿名関数の1つにラムダ式がある。
  • 匿名関数の1つである匿名メソッドよりも簡潔に記述することができる。
  • C# 3.0以降であれば、ラムダ式を使用すべきである。


コンパイラは、匿名関数の記述に該当するクラスメソッドまたはインスタンスメソッドにおいて、必要であれば新たにクラスを生成する。
匿名関数によるデリゲートは、コンパイラが生成したメソッドを参照するデリゲートとして作成される。

デリゲートの作成において、C# 3.0で追加されたラムダ式を使用することで、匿名メソッドよりも簡潔に記述できる。
また、IEnumerable<T>を実装したクラスであればインスタンスメソッドとして記述できる。

<syntaxhighlight lang="csharp">
// 匿名メソッド
Func<string, bool> predicate = delegate (string str)
{
   return str.Length < 5;
};

// ラムダ式
Func<string, bool> predicate = (string str) =>
{
   return str.Length < 5;
};

// ラムダ式を更に短く記述
Func<string, bool> predicate = str => str.Length < 5;
</source>


<syntaxhighlight lang="csharp">
public static void Main (String[] args)
{
   var names = new List<string>
   {
      "Taro",
      "Jiro",
      "Saburo"
   };

   int count = CountList(names, str => str.Length < 5);
}
</source>


以下の例では、LINQのCountメソッドを記述している。
LINQのCountメソッドはIEnumerable<T>型の拡張メソッドであり、引数はFunc<TSource, bool>型である。
Countメソッドは、nullチェック等の処理も含まれている。

<syntaxhighlight lang="csharp">
int count = names.Count(str => str.Length < 5);
</source>


また、拡張メソッドと型推論を知ることで、LINQをより理解することができる。

式木データ

ラムダ式は、式木データとして扱うこともできる。

Pred p = n => n > 0;のように、デリゲートに代入する場合は、ラムダ式は匿名メソッドと同じ扱いとなる。
これに対して、ラムダ式をExpression型の変数に代入すると、式木データとして扱うことができ、
以下のように、式中の項を取得するといった操作ができる。

<syntaxhighlight lang="csharp">
Expression<Func<int, bool>> e = n => n > 0;
BinaryExpression lt = (BinaryExpression)e.Body;
ParameterExpression en = (ParameterExpression)lt.Left;
ConstantExpression zero = (ConstantExpression)lt.Right;
</source>


インタプリタ型の関数型言語には、匿名関数と式木データを区別しないものがあり、ある時はラムダ式を匿名関数として、またある時は式木データとして使用することができる。
C#では、デリゲートまたはExpression型によりコンパイル結果を変更することで、関数型言語と似たような動作を実現している。

ただし、ラムダ式をデリゲートに代入する場合と違い、式木データには制約がある。
式木データに代入できるものは、単文の({}を使用しない)ラムダ式だけである。
したがって、四則演算やメソッドコールは式木データにできるが、forやwhile等の制御構文は式木データにできない。
(Expression型には、forやwhile等に相当するノードはない)

以下の例では、1つ目のラムダ式はコンパイル可能であるが、2つ目はコンパイルエラーになる。

<syntaxhighlight lang="csharp">
// コンパイル可能
Expression<Func<int, bool>> p = n => n > 0;

// コンパイルエラー
Expression<Func<int, int, int>> f =
(x, y) =>
{
   int sum = x + y;
   int prod = x * y;
   return sum * prod;
};
</source>


また、LINQ to SQLでは、ラムダ式の式木データを使用して、LINQクエリ式の条件式等を式木データとして取得し、
それをSQLクエリに変換して、データベースに問い合わせをかける。

例えば、以下のようなLINQクエリ式の場合、db.Whereやdb.Selectでは、
データベースサーバに対して以下のようなSQLを発行する仕組みになっている。

これは、c.City == "London"の部分を式木データとして取得して、その内容を見ながらSQL文を生成している。

<syntaxhighlight lang="csharp">
// LINQクエリ式
var q = from c in db
        where c.City == "London"
        select new {c.City};

foreach (var city in q)
{
  // do something

}

// 等価のSQL文
SELECT TOP 1 [t0].[City]
FROM [Customers] AS [t0]
WHERE [t0].[City] = @p0
</source>