C Sharpの基礎 - ラムダ式
概要
ラムダ式とは、関数(メソッド)を整数等の変数と全く同列に扱う手法のことである。
- デリゲートに対して代入すると、匿名メソッドと同じ扱いになる。
ラムダ式は、匿名メソッドと比較して、より簡潔に記述することができる。 - Expression型の変数に代入すると、式木データになる。
ラムダ式
匿名関数
ラムダ式の特徴を以下に示す。
- 匿名関数を使用することにより、インラインでデリゲートの定義を記述し、デリゲートを作成できる。
- 匿名関数の1つにラムダ式がある。
- 匿名関数の1つである匿名メソッドよりも簡潔に記述することができる。
- C# 3.0以降であれば、ラムダ式を使用すべきである。
コンパイラは、匿名関数の記述に該当するクラスメソッドまたはインスタンスメソッドにおいて、必要であれば新たにクラスを生成する。
匿名関数によるデリゲートは、コンパイラが生成したメソッドを参照するデリゲートとして作成される。
デリゲートの作成において、C# 3.0で追加されたラムダ式を使用することで、匿名メソッドよりも簡潔に記述できる。
また、IEnumerable<T>を実装したクラスであればインスタンスメソッドとして記述できる。
// 匿名メソッド
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;
Action
Actionは、パラメータを受け取り、戻り値を返さないデリゲート (メソッドを参照する型) である。
戻り値がない (voidを返す) メソッドを表現する場合に使用する。
引数は0〜16個までのパラメータを受け取ることが可能である。
コンソール出力やデータの保存等の処理に使用されることが多い。
マルチスレッドでGUIを処理する場合にも、invokeメソッドと同時に使用することがある。
詳細は、C_Sharpの基礎_-_マルチスレッドとGUI#Invokeメソッドを使うを参照すること。
// Actionの基本
Action sayHello = () => Console.WriteLine("Hello, Action");
sayHello(); // 出力: Hello, Action
// 引数が1つのAction
Action<string> greet = (name) => Console.WriteLine($"Hello, {name}!");
greet("John"); // 出力: Hello, John!
// 引数を2つのAction
Action<int, int> printSum = (x, y) => Console.WriteLine($"Sum: {x + y}");
printSum(5, 3); // 出力: Sum: 8
// コレクション処理 (リストの各要素を2倍にする)
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
Action<int> twiceNumber = (n) => Console.WriteLine($"Number: {n * 2}");
numbers.ForEach(twiceNumber);
// イベントハンドリング
Action<string> logMessage = (message) => Console.WriteLine($"Log: {message}");
// メソッドの引数にする
void ProcessItems<T>(IEnumerable<T> items, Action<T> processor)
{
foreach (var item in items)
{
processor(item);
}
}
また、Actionを使用してラムダ式の即時実行を行うこともできる。
// 括弧を使用した即時実行
new Action<int>(x =>
{
x++;
})(1);
// Invokeメソッドを使用した即時実行
new Action<int>(x =>
{
x++;
}).Invoke(1);
// ローカル変数を使用・変更する場合
int result = 1;
new Action<int>(x =>
{
result = x + 1;
Console.WriteLine($"値 : {result}");
})(1);
Func
Funcとは、パラメータを受け取り、値を返すデリゲートである。
最後の型パラメータが戻り値の型を表す。
0〜16個までの入力パラメータを受け取ることができる。
Funcは、値の変換や計算処理によく使用されている。
// Funcの基本
Func<int> getRandomNumber = () => new Random().Next(1, 100);
int random = getRandomNumber(); // 1〜100までのランダムな数値を取得
// 引数を1つ受け取り、戻り値を返すFunc
Func<int, bool> isEven = (number) => number % 2 == 0;
bool result = isEven(4); // true
// 引数が2つでdouble型の戻り値を返すFunc
Func<int, int, double> calculateAverage = (x, y) => (x + y) / 2.0;
double average = calculateAverage(10, 15); // 12.5
// コレクション処理 (リストの要素を変換)
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
Func<int, string> numberToString = (n) => $"Value: {n}";
List<string> stringNumbers = numbers.Select(numberToString).ToList();
// 条件付き処理
Func<int, string> gradeScore = (score) => {
if (score >= 90) return "A";
if (score >= 80) return "B";
if (score >= 70) return "C";
if (score >= 60) return "D";
return "F";
};
int studentScore = 85;
string grade = gradeScore(studentScore); // "B"
// 引数にFuncを渡す
public static T TransformData<T>(string input, Func<string, T> transformer)
{
return transformer(input);
}
コレクション処理
var names = new List<string>
{
"Taro",
"Jiro",
"Saburo"
};
int count = CountList(names, str => str.Length < 5);
LINQとの連携
LINQの各メソッドと組み合わせることにより、効率的なコレクション処理が可能である。
以下の例では、LINQのCountメソッドを記述している。
拡張メソッドであるCountメソッドは、IEnumerable<T>型の拡張メソッドであり、引数はFunc<TSource, bool>型である。
また、Countメソッドは、nullチェック等の処理も含まれている。
int count = names.Count(str => str.Length < 5);
また、拡張メソッドと型推論を知ることで、LINQをより理解することができる。
LINQの詳細は、C_Sharpの基礎 - LINQを参照すること。
式木データ
ラムダ式は、式木データとして扱うこともできる。
Pred p = n => n > 0;
のように、デリゲートに代入する場合は、ラムダ式は匿名メソッドと同じ扱いとなる。
これに対して、ラムダ式をExpression
型の変数に代入すると、式木データとして扱うことができ、
以下のように、式中の項を取得するといった操作ができる。
Expression<Func<int, bool>> e = n => n > 0;
BinaryExpression lt = (BinaryExpression)e.Body;
ParameterExpression en = (ParameterExpression)lt.Left;
ConstantExpression zero = (ConstantExpression)lt.Right;
インタプリタ型の関数型言語には、匿名関数と式木データを区別しないものがあり、ある時はラムダ式を匿名関数として、またある時は式木データとして使用することができる。
C#では、デリゲートまたはExpression型によりコンパイル結果を変更することで、関数型言語と似たような動作を実現している。
ただし、ラムダ式をデリゲートに代入する場合と違い、式木データには制約がある。
式木データに代入できるものは、単文の({}を使用しない)ラムダ式だけである。
したがって、四則演算やメソッドコールは式木データにできるが、forやwhile等の制御構文は式木データにできない。
(Expression型には、forやwhile等に相当するノードはない)
以下の例では、1つ目のラムダ式はコンパイル可能であるが、2つ目はコンパイルエラーになる。
// コンパイル可能
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;
};
また、LINQ to SQLでは、ラムダ式の式木データを使用して、LINQクエリ式の条件式等を式木データとして取得し、
それをSQLクエリに変換して、データベースに問い合わせをかける。
例えば、以下のようなLINQクエリ式の場合、db.Whereやdb.Selectでは、
データベースサーバに対して以下のようなSQLを発行する仕組みになっている。
これは、c.City == "London"
の部分を式木データとして取得して、その内容を見ながらSQL文を生成している。
// 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
デフォルト引数
C# 12以降、ラムダ式の引数に既定値を与えることができる。
// 既定値の指定
var fn = (int x = 10) => 1;
また、params引数も使用することができる。
// params引数
var fn = (params int[] y) => 1;
既定値とparams引数も同時に使用することができる。
var fn = (int x = 1, params int[] y) => 1;