ここではC#における匿名メソッドと、それを簡潔に記述するための方法であるラムダ式について説明します。C#を使う大きなメリットであるLINQを理解するためにも、匿名メソッドやラムダ式は必須の概念になるのでしっかり理解しておきましょう。
匿名メソッドとは?
匿名メソッドは、メソッド名を宣言せずに直接処理を記述したメソッドであり、デリゲートのインスタンスに代入して用いられます。
例えばあるメソッドをデリゲートに格納してカプセル化して、他のメソッドの引数として渡すような例を考えてみてください。このような特定の処理からしか呼び出されないメソッドは、そもそもメソッド名を宣言することすら冗長になります。もちろん、特定の処理からしか呼び出されないようなメソッドもとりあえず適当に名前を付けて普通のメソッドと同じように宣言しても良いのですが、それが膨大な数になるとよく使うメソッドが適当に名前を付けたメソッドに埋もれてしまって可読性の低下につながってしまいます。また、普通のメソッドと同じように宣言していると、想定しないところからそのメソッドを呼び出されてしまって、バグの温床になる可能性もあります。そのようなことを避けるために匿名メソッドが用意されています。
なお、匿名メソッドには「外部変数のキャプチャ」という特殊な機能も備わっていますが、ここではその説明は省略します。
delegateキーワードを用いて匿名メソッドを宣言する
匿名メソッドの宣言
基本書式
delegate([データ型] [引数1], [データ型] [引数2], ... )
{
[処理]
}
コード例
//匿名メソッドの宣言
Predicate<int> judge = delegate (int n) { return n <= 10; };
//匿名メソッドの実行
Console.WriteLine(judge(5));
delegateキーワードに続いて( )内に引数の型のリストを、{ }内に処理を記述することで、「引数とその処理の組み合わせ」を定義することができます。これはメソッドでありながらその名前を宣言していないことから、匿名メソッドと呼ばれています。この匿名メソッドを実行するには、それを何らかのデリゲートインスタンスに代入する必要があります。
コード例では「int型の引数nが10以下であればtrueを返し、そうでなければfalseを返す」という処理をPredicate型のデリゲートのjudgeに代入しています。そして、5行目で「5」という数値を引数にとって実行し、「5」がその条件を満たすかどうかを判定しています。
なお、引数がない場合は以下のようにdelegate以下の( )を省略することも可能です。
基本書式
delegate
{
[処理]
}
匿名メソッドの活用方法
先ほどのコード例では匿名メソッドと言いつつ結局judgeというデリゲートインスタンス名をつけて実行しているので、匿名メソッドを使うメリットをあまり感じない方も多いかもしれません。
匿名メソッドの使用価値が出てくるのは、次のようにデリゲートを引数として受け付けるメソッドを作成し、そのメソッドの引数に直接匿名メソッドを指定するような場合です。
class Program
{
// int型の配列のうち、条件(judge)を満たすものの個数を数える
public int Count(int[] numbers, Predicate<int> judge)
{
int counter = 0;
foreach (int number in numbers)
{
if (judge(number)) counter++;
}
return counter;
}
static void Main(string[] args)
{
Program program = new Program();
var numbers = new[] { 1, 4, 14, 10, 8, 12 };
//匿名メソッドを引数に直接指定
var count = program.Count(numbers, delegate (int n) { return n <= 10; });
Console.WriteLine(count);
}
}
この例では4-12行目で宣言しているCountメソッドでPredicate<int>型を引数として受け取ることができるようにしています。先ほどのコード例では匿名メソッドをPredicate<int>型のデリゲートインスタンスに代入していましたが、ここではPredicate<int>型の引数として匿名メソッドを指定しています(19行目)。これで、17行目で指定した数値の配列のうち、「10以下」という条件を満たすものの個数を数えることができました。
ここで最初に宣言したCountメソッドではカウントの条件を指定せずに、Countメソッドを実行するときに引数で条件を指定していることが大きなポイントになります。つまりCountメソッドの条件を引数で直接指定するようにして、Countメソッドを抽象化しています。
ラムダ式の導入
先ほどの例で以下のような匿名メソッドを使いました。
delegate (int n) { return n <= 10; };
しかし、これは「引数とその処理の組み合わせ」しか表していないにもかかわらず、delegateキーワードが必要だったりと若干冗長な表現です。匿名メソッドは「引数とその処理の組み合わせ」なのだから、単純に「引数」と「処理」だけをつなげて記述することはできないでしょうか。C# 3.0からはその要望に応える方法としてラムダ式が導入されました。
基本書式
([データ型] [引数1], [データ型] [引数2], ... ) => { [処理] };
コード例
(int n) => { return n <= 10; };
ラムダ演算子(=>)を用いて、左辺に引数のリストを書き、右辺にその引数を用いた処理を記述します。なお、引数がない場合には、左辺に()だけを記述します。これで先ほどの匿名メソッドが「引数」と「処理」だけになりだいぶすっきりしました。しかし、このラムダ式はさらに以下のように簡略化が可能です。
ラムダ式の簡略表現 | |
---|---|
左辺 | 引数が一つであれば()は省略可能 引数の型が推論できる場合には、型を省略できる |
右辺 | { }の中が1つのステートメントの場合は、{ }とreturnを省略できる |
ラムダ式を用いて匿名メソッドを宣言する
それではラムダ式を用いて先ほどの匿名メソッドを書き直してみましょう。
//delegateキーワードを用いた宣言
Predicate<int> judge = delegate (int n) { return n <= 10; };
//ラムダ式を用いた宣言
Predicate<int> judge = n => n <= 10;
ラムダ式を用いた表現があまりにも簡略過ぎて驚くかも知れませんが、順番に説明していきましょう。まず簡略表現を使わずにラムダ式で書くと
(int n) => { return n <= 10; };
となります。しかし、これをPredicate<int> judgeに代入するので、引数の型がintであるのは型推論が可能であり匿名メソッドの引数の型名は省略可能になります。また引数は1つしかないので、左辺の( )は省略可能です。さらに、右辺は1つのステートメントから成るので{ }とreturnも省略可能です。以上より
n => n <= 10;
と表現可能になります。
最後に、ラムダ式で書かれた匿名メソッドをメソッドの引数に指定するコードを載せておきます。
class Program
{
// int型の配列のうち、条件(judge)を満たすものの個数を数える
public int Count(int[] numbers, Predicate<int> judge)
{
int counter = 0;
foreach (int number in numbers)
{
if (judge(number)) counter++;
}
return counter;
}
static void Main(string[] args)
{
Program program = new Program();
var numbers = new[] { 1, 4, 14, 10, 8, 12 };
//匿名メソッドを引数に直接指定
var count = program.Count(numbers, n => n <= 10);
Console.WriteLine(count);
}
}
ラムダ式を使わずにdelegateキーワードを用いて匿名メソッドを宣言する方法はC# 2.0までの方法で、C# 3.0でラムダ式が導入されてからはdelegateキーワードを用いて匿名メソッドを宣言する意味はあまりありません。ラムダ式を使えば非常に簡潔にコーディングすることが可能になるので、ぜひこちらをマスターしましょう。
コメント