1. はじめに
プログラミングしていると、1 回しか使わない単純な処理をその場で書きたいことがあるよね。専用のメソッドを作るのは、飛行機組み立てキットのハンマーで釘を打つようなもの。もっと気楽にその場で片付けたいってことがよくある。
匿名メソッドは、その場で使うための一時的なコード片で、名前は不要って感じ。特徴はこんな感じ:
- 他のメソッドの内部で宣言される。
- 名前がない。
- たいてい一度だけ使う — 例えば delegate に渡すかイベントにサブスクライブするために使う。
最近は ラムダ式(演算子 =>)の方が頻繁に使われるけど、匿名メソッドを理解すると delegate やイベントモデルのしくみがよく分かるよ。
どんなときに匿名メソッドが便利?
- ソートやフィルタ、イベント処理など、パラメータにロジックをさっと渡したいとき。
- 使い捨てのロジックのためにクラスに余分なメソッドを増やしたくないとき。
- コードをコンパクトで読みやすくしたいとき:小さなビジネスロジックがその場にまとまるからね。
ちょっと歴史的な話
C# に匿名メソッドが入ったのはバージョン 2.0 からだよ。それ以前は delegate を使うのが面倒で、たとえその場でしか使わない処理でも名前付きメソッドを用意する必要があった。匿名メソッドでだいぶ楽になって、ラムダ式が出てきてからさらに簡潔になったんだ。
2. 定番:delegate と通常の「名前付き」メソッド
匿名メソッドに入る前に、delegate を介して振る舞いを渡す標準的な方法を思い出そう。例として「書籍データベース」アプリがあって、いろんな条件で本をフィルタしたいとするよ。
// デリゲートを定義する
public delegate bool BookFilter(Book book);
// 書籍クラス
public class Book
{
public string Title { get; set; }
public int Year { get; set; }
}
以前はこんな風に書いてた:
// 別のフィルタ関数
public static bool IsClassic(Book b)
{
return b.Year < 1970;
}
// どこかで利用
BookFilter filter = IsClassic;
毎回別メソッドを用意するのは面倒だよね。フィルタが何十個もあると最悪だ。
3. 匿名メソッド:最小限で delegate と仲良し
匿名メソッドを使うと、フィルタのコードを欲しい場所で直接書けるよ:
BookFilter filter = delegate(Book b)
{
return b.Year < 1970;
};
これだけ!余計なメソッドは不要で、すべてその場に書ける。delegate は名前のない関数の宣言として振る舞うんだ。
一般的な構文
delegate([引数])
{
// メソッド本体
};
プログラムに埋め込む例:
public class Book
{
public string Title { get; set; }
public int Year { get; set; }
}
public delegate bool BookFilter(Book book);
class Program
{
static void Main()
{
Book[] books = {
new Book { Title = "マスターとマーガリータ", Year = 1967 },
new Book { Title = "Clean Code", Year = 2008 }
};
// クラシック向けの匿名フィルタ
BookFilter filter = delegate(Book b)
{
return b.Year < 1970;
};
foreach (Book book in books)
{
if (filter(book)) // 匿名関数を呼び出すよ!
Console.WriteLine($"{book.Title} — 名作だ!");
}
}
}
出力:
マスターとマーガリータ — 名作だ!
4. いろんな delegate と匿名メソッド
匿名メソッドはあらゆる delegate と一緒にうまく動くよ。例えば標準の Action や Func<T, TResult> なんかも使える(これは後で詳しくやる)。
Action<string> sayHello = delegate(string name)
{
Console.WriteLine($"やあ、{name}!");
};
sayHello("世界"); // やあ、世界!
説明:使い捨ての匿名メソッドのイメージ
匿名メソッドは短期の作業者のようなもの:ちょっと来て仕事をして、終わったらいなくなる。delegate が作業者と仕事を結びつける感じ。
Func<int, int, int> sum = delegate (int a, int b) {
return a + b;
};
Console.WriteLine(sum(5, 7)); // 12
用途:ソート、検索、コレクション処理
例えば、リストを発行年でソートしたいとする。使い捨ての比較関数のために別メソッドを作るのは過剰だよね。
var books = new List<Book>
{
new Book { Title = "マスターとマーガリータ", Year = 1967 },
new Book { Title = "Clean Code", Year = 2008 }
};
books.Sort(delegate(Book a, Book b)
{
return a.Year.CompareTo(b.Year);
});
foreach (var book in books)
Console.WriteLine($"{book.Title} ({book.Year})");
5. 便利なポイント
匿名メソッドとラムダ式は同じもの?
最近の C# では ラムダ式(=>)が多用されるし、見た目も似ているけど、いくつか違いはある:
- ラムダ式の方が短くて表現力が高い。
- ラムダは変数キャプチャのルールが明確(これは後で学ぶ)。
- 匿名メソッドは歴史的に先に来て、レガシーコードで見かけることがある。
同じ例をラムダで書くとこんな感じ:
BookFilter filter = b => b.Year < 1970;
でも古い構文も新しい構文も両方知っておくと、面接や他人のコードを読むときに役立つよ。
引数あり・なしの匿名メソッド
delegate が引数を取らないなら、一行で書けるよ:
Action printHello = delegate { Console.WriteLine("やあ!"); };
printHello(); // やあ!
引数があっても一行で書けることが多い:
Action<int> printSquare = delegate (int x) { Console.WriteLine(x * x); };
printSquare(6); // 36
匿名メソッド vs ラムダ式
| 匿名メソッド | ラムダ式 | |
|---|---|---|
| 構文 | |
|
| 変数のキャプチャ | あり | あり |
| 普及度 | あまり使われない | 標準的 |
| 戻り値 | あり/必須 | あり/必須 |
| 複数行対応 | 可能 | 可能 |
6. 匿名メソッドの細かい注意点
ローカル変数のキャプチャ
匿名メソッドは外側のメソッドの変数を使えるよ — これをクロージャ(キャプチャ)って呼ぶ。例えば:
int minYear = 1970;
BookFilter filter = delegate(Book book)
{
return book.Year < minYear;
};
Console.WriteLine(filter(new Book { Title = "Test", Year = 1960 })); // True
あとから minYear を変えると、フィルタはその新しい値を使うから注意してね!
引数を省略できる
引数が不要ならこんな感じ:
Action sayHi = delegate { Console.WriteLine("Hi!"); };
null の渡し方
delegate にメソッドが割り当てられていない(例えば匿名メソッドがない)ときは値が null になる。呼び出すと NullReferenceException になるから気をつけて。
複数行の処理
匿名メソッドは条件文やループ、他の呼び出しを含む完全なブロックを書けるよ:
Action manyThings = delegate
{
Console.WriteLine("始めたよ!");
for (int i = 0; i < 3; i++)
Console.WriteLine(i);
Console.WriteLine("終わり!");
};
manyThings();
7. 典型的なミスと挙動
ときどきプログラマは変数のスコープで迷う:ループやネストしたメソッドから変数をキャプチャすると予想外の結果になることがあるよ。
List<Action> actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
actions.Add(delegate { Console.WriteLine(i); });
}
foreach (var action in actions) action(); // 3 3 3 — サプライズ!
ポイントは、匿名メソッドは変数 i を「参照している」こと。ループが終わると i は 3 になっているから、すべて同じ値を出力するんだ。正しく捕まえるにはこんな感じでやると良い:
for (int i = 0; i < 3; i++)
{
int current = i;
actions.Add(delegate { Console.WriteLine(current); });
}
GO TO FULL VERSION