C#の匿名関数とは?ラムダ式との違い・使い方・実践例を初心者向けに徹底解説
はじめに
C#でプログラムを書いていると、「匿名関数」や「ラムダ式」という言葉を目にすることがあります。特に、Listの絞り込み、LINQ、イベント処理、非同期処理などを使う場面では、匿名関数がよく登場します。
しかし、初心者の方にとっては「匿名関数とは何か」「ラムダ式と何が違うのか」「delegateやFunc、Actionとどう関係するのか」がわかりにくいポイントです。
この記事では、C#の匿名関数について、基本から実践例、ラムダ式との違い、よくあるエラーまで初心者向けにわかりやすく解説します。C#でよく使われる匿名関数を理解すると、LINQやイベント処理のコードが読みやすくなり、より実践的なC#プログラミングができるようになります。
1. C#の匿名関数とは?初心者向けにわかりやすく解説
1-1. 匿名関数とは「名前を付けずに定義できる関数」
C#の匿名関数とは、名前を付けずにその場で定義できる関数のことです。
通常、C#で処理をまとめる場合は、次のようにメソッドを定義します。
C#static int Add(int a, int b)
{
return a + b;
}
この場合、Addという名前が付いています。
一方、匿名関数は次のように名前を付けずに処理を定義できます。
C#Func<int, int, int> add = (a, b) => a + b;
Console.WriteLine(add(3, 5)); // 8
この例では、(a, b) => a + bの部分が匿名関数です。関数そのものには名前がありませんが、addという変数に代入して呼び出しています。
つまり、匿名関数は「一時的に使いたい処理を、その場で簡潔に書くための仕組み」と考えるとわかりやすいです。
1-2. 匿名関数が使われる主な場面
C#の匿名関数は、次のような場面でよく使われます。
C#List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
var evenNumbers = numbers.Where(n => n % 2 == 0);
foreach (var number in evenNumbers)
{
Console.WriteLine(number);
}
このコードでは、n => n % 2 == 0が匿名関数です。Listの中から偶数だけを取り出す条件として使われています。
匿名関数は主に、次のような場面で使われます。
・配列やListの絞り込み
・LINQのWhereやSelect
・イベント処理
・コールバック処理
・非同期処理
・一時的な計算処理
・並び替え条件の指定
特にLINQを使うときは、匿名関数やラムダ式を理解しておくとコードの意味がかなり読みやすくなります。
1-3. 通常のメソッドとの違い
通常のメソッドと匿名関数の大きな違いは、「名前を持つかどうか」と「定義する場所」です。
通常のメソッドは、クラスの中に名前を付けて定義します。
C#static bool IsEven(int number)
{
return number % 2 == 0;
}
このメソッドを使ってListを絞り込む場合は、次のように書けます。
C#List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
var evenNumbers = numbers.Where(IsEven);
一方、匿名関数を使うと、メソッドを別に定義せず、その場で条件を書けます。
C#var evenNumbers = numbers.Where(n => n % 2 == 0);
処理が短く、一度しか使わない場合は匿名関数の方が簡潔です。
ただし、処理が長い場合や、何度も使い回す場合は、通常のメソッドとして定義した方が読みやすくなります。
1-4. C#における匿名関数の2つの書き方
C#の匿名関数には、大きく分けて2つの書き方があります。
1つ目は「匿名メソッド」です。
C#Action<string> greet = delegate(string name)
{
Console.WriteLine($"こんにちは、{name}さん");
};
greet("田中");
2つ目は「ラムダ式」です。
C#Action<string> greet = name =>
{
Console.WriteLine($"こんにちは、{name}さん");
};
greet("田中");
どちらも名前のない関数を定義する書き方ですが、現在のC#ではラムダ式の方がよく使われます。
2. C#の匿名関数には「匿名メソッド」と「ラムダ式」がある
2-1. 匿名メソッドとは
匿名メソッドとは、delegateキーワードを使って名前のないメソッドを定義する書き方です。
C#Action message = delegate()
{
Console.WriteLine("匿名メソッドです");
};
message();
このコードでは、delegate()から始まる部分が匿名メソッドです。
引数を受け取る場合は、次のように書きます。
C#Action<string> greet = delegate(string name)
{
Console.WriteLine($"こんにちは、{name}さん");
};
greet("佐藤");
匿名メソッドは、ラムダ式が広く使われる前から存在している書き方です。古いC#のコードや、既存のプロジェクトでは見かけることがあります。
2-2. ラムダ式とは
ラムダ式とは、=>を使って匿名関数を簡潔に書く構文です。
C#Func<int, int> square = x => x * x;
Console.WriteLine(square(4)); // 16
x => x * xは、「xを受け取って、x * xを返す関数」という意味です。
複数の引数を使う場合は、次のように書きます。
C#Func<int, int, int> add = (a, b) => a + b;
Console.WriteLine(add(2, 3)); // 5
処理が複数行になる場合は、波かっこを使います。
C#Func<int, int, int> add = (a, b) =>
{
int result = a + b;
return result;
};
ラムダ式は、C#のLINQやイベント処理で非常によく使われる書き方です。
2-3. 匿名メソッドとラムダ式の関係
匿名メソッドとラムダ式は、どちらも匿名関数を表す書き方です。
つまり、匿名関数という大きな概念の中に、「匿名メソッド」と「ラムダ式」が含まれていると考えるとわかりやすいです。
C#// 匿名メソッド
Action hello1 = delegate()
{
Console.WriteLine("Hello");
};
// ラムダ式
Action hello2 = () =>
{
Console.WriteLine("Hello");
};
どちらも名前のない処理を変数に代入しています。
ただし、ラムダ式の方が短く書けるため、現在のC#ではラムダ式が主流です。
2-4. 現在のC#ではラムダ式がよく使われる理由
現在のC#でラムダ式がよく使われる理由は、コードを短く、読みやすく書けるからです。
例えば、Listから偶数だけを取り出す処理を匿名メソッドで書くと、次のようになります。
C#var evenNumbers = numbers.Where(delegate(int n)
{
return n % 2 == 0;
});
ラムダ式を使うと、同じ処理を次のように書けます。
C#var evenNumbers = numbers.Where(n => n % 2 == 0);
ラムダ式の方が短く、条件もひと目でわかります。
また、LINQとの相性が非常に良いため、C#ではラムダ式を使う機会が多くなっています。
3. 匿名関数とラムダ式の違い
3-1. 書き方・構文の違い
匿名関数は、名前のない関数全体を指す言葉です。その中に、匿名メソッドとラムダ式があります。
匿名メソッドの書き方は次のとおりです。
C#Action<string> print = delegate(string text)
{
Console.WriteLine(text);
};
ラムダ式の書き方は次のとおりです。
C#Action<string> print = text =>
{
Console.WriteLine(text);
};
また、処理が1行の場合はさらに短く書けます。
C#Action<string> print = text => Console.WriteLine(text);
匿名メソッドはdelegateキーワードを使いますが、ラムダ式は=>を使います。
初心者のうちは、「=>が出てきたらラムダ式」と覚えておくとよいでしょう。
3-2. コードの読みやすさの違い
短い処理を書く場合、ラムダ式の方が読みやすくなります。
C#var names = new List<string> { "Tanaka", "Sato", "Suzuki" };
var result = names.Where(name => name.StartsWith("S"));
このコードは、「nameがSで始まるものを取り出す」という意味がすぐにわかります。
一方、匿名メソッドで書くと次のようになります。
C#var result = names.Where(delegate(string name)
{
return name.StartsWith("S");
});
同じ処理ですが、ラムダ式の方が簡潔です。
ただし、処理が複雑になりすぎると、ラムダ式でも読みにくくなります。その場合は通常のメソッドに分ける方がよいです。
3-3. LINQで使いやすいのはラムダ式
LINQでは、ラムダ式が非常によく使われます。
C#List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
var result = numbers
.Where(n => n >= 3)
.Select(n => n * 10);
foreach (var number in result)
{
Console.WriteLine(number);
}
この例では、Whereで条件に合う要素を絞り込み、Selectで値を変換しています。
C#.Where(n => n >= 3)
.Select(n => n * 10)
このように、LINQでは「各要素に対して何をするか」をラムダ式で表現します。
C#でLINQを使うなら、ラムダ式の理解はほぼ必須といえます。
3-4. 匿名メソッドを使うケースはあるのか
現在のC#では、匿名メソッドよりもラムダ式がよく使われます。
ただし、匿名メソッドをまったく使わないわけではありません。例えば、古いC#のコードを読むときや、既存プロジェクトで昔から書かれているコードを保守するときに登場することがあります。
C#button.Click += delegate(object sender, EventArgs e)
{
Console.WriteLine("クリックされました");
};
このようなコードは、古いサンプルや既存コードで見かけることがあります。
新しくコードを書く場合は、基本的にはラムダ式を使うとよいでしょう。
C#button.Click += (sender, e) =>
{
Console.WriteLine("クリックされました");
};
3-5. 初心者はどちらを覚えるべきか
初心者がまず覚えるべきなのはラムダ式です。
理由は、現在のC#でよく使われているからです。特に、LINQ、List操作、イベント処理、非同期処理などではラムダ式が頻繁に登場します。
ただし、匿名メソッドの存在も知っておくと、古いコードを読んだときに困りません。
学習の順番としては、次の流れがおすすめです。
ラムダ式の基本を覚える
Func・Action・Predicateを理解する
LINQでラムダ式を使う
匿名メソッドは「delegateを使う古い書き方」として理解する
この順番で学ぶと、C#の匿名関数を実践的に使いやすくなります。
4. C#の匿名関数の基本的な使い方
4-1. delegateを使った匿名メソッドの書き方
匿名メソッドは、delegateキーワードを使って書きます。
C#Action hello = delegate()
{
Console.WriteLine("こんにちは");
};
hello();
このコードでは、helloという変数に匿名メソッドを代入しています。
引数を受け取る場合は、次のように書きます。
C#Action<string> greet = delegate(string name)
{
Console.WriteLine($"こんにちは、{name}さん");
};
greet("山田");
戻り値がある場合は、Funcを使うと便利です。
C#Func<int, int, int> add = delegate(int a, int b)
{
return a + b;
};
Console.WriteLine(add(10, 20)); // 30
匿名メソッドは、delegate(引数)のあとに処理を波かっこで書くのが基本です。
4-2. ラムダ式を使った匿名関数の書き方
ラムダ式は、=>を使って書きます。
C#Action hello = () => Console.WriteLine("こんにちは");
hello();
引数がない場合は、() =>と書きます。
引数が1つの場合は、かっこを省略できます。
C#Action<string> greet = name => Console.WriteLine($"こんにちは、{name}さん");
greet("鈴木");
引数が2つ以上ある場合は、かっこが必要です。
C#Func<int, int, int> add = (a, b) => a + b;
Console.WriteLine(add(3, 7)); // 10
ラムダ式は短い処理を簡潔に書けるため、C#では非常によく使われます。
4-3. 引数がある匿名関数の書き方
引数がある匿名関数は、次のように書きます。
C#Action<string> printName = name =>
{
Console.WriteLine($"名前: {name}");
};
printName("田中");
この例では、nameが引数です。
引数が複数ある場合は、次のように書きます。
C#Action<string, int> printUser = (name, age) =>
{
Console.WriteLine($"{name}さんは{age}歳です");
};
printUser("佐藤", 25);
Action<string, int>は、「string型とint型の引数を受け取り、戻り値はない処理」を表します。
4-4. 戻り値がある匿名関数の書き方
戻り値がある匿名関数には、Funcを使います。
C#Func<int, int> doubleValue = x => x * 2;
Console.WriteLine(doubleValue(5)); // 10
Func<int, int>は、「int型の引数を受け取り、int型の値を返す関数」という意味です。
複数の引数と戻り値がある場合は、最後の型が戻り値の型になります。
C#Func<int, int, int> multiply = (a, b) => a * b;
Console.WriteLine(multiply(4, 6)); // 24
この場合、最初のintが1つ目の引数、2つ目のintが2つ目の引数、最後のintが戻り値の型です。
4-5. 複数行の処理を書く方法
ラムダ式で複数行の処理を書く場合は、波かっこを使います。
C#Func<int, int, string> checkScore = (score, passLine) =>
{
if (score >= passLine)
{
return "合格";
}
return "不合格";
};
Console.WriteLine(checkScore(80, 60));
複数行のラムダ式では、戻り値がある場合にreturnを書く必要があります。
1行の場合は、次のようにreturnを省略できます。
C#Func<int, int, string> checkScore = (score, passLine) => score >= passLine ? "合格" : "不合格";
短い処理なら1行のラムダ式、少し複雑な処理なら複数行のラムダ式を使うとよいでしょう。
ただし、あまりにも長くなる場合は、通常のメソッドに分ける方が読みやすくなります。
5. Func・Action・Predicateと匿名関数の関係
5-1. Funcとは
Funcは、戻り値がある処理を表すdelegate型です。
例えば、次のコードを見てください。
C#Func<int, int> square = x => x * x;
Console.WriteLine(square(5)); // 25
Func<int, int>は、「int型の引数を1つ受け取り、int型を返す関数」という意味です。
複数の引数を使う場合は、次のように書きます。
C#Func<int, int, int> add = (a, b) => a + b;
Console.WriteLine(add(2, 8)); // 10
Func<int, int, int>では、最後のintが戻り値の型です。
つまり、Func<T, TResult>のように、最後に書いた型が戻り値になります。
5-2. Actionとは
Actionは、戻り値がない処理を表すdelegate型です。
C#Action hello = () =>
{
Console.WriteLine("こんにちは");
};
hello();
引数を受け取る場合は、次のように書きます。
C#Action<string> greet = name =>
{
Console.WriteLine($"こんにちは、{name}さん");
};
greet("高橋");
複数の引数も指定できます。
C#Action<string, int> introduce = (name, age) =>
{
Console.WriteLine($"{name}さんは{age}歳です");
};
introduce("中村", 30);
Actionは戻り値がないため、画面に表示する、ログを出す、イベント時に処理を実行する、といった場面で使いやすいです。
5-3. Predicateとは
Predicateは、条件判定を表すdelegate型です。
C#Predicate<int> isEven = n => n % 2 == 0;
Console.WriteLine(isEven(4)); // True
Console.WriteLine(isEven(5)); // False
Predicate<int>は、「int型の値を受け取り、bool型を返す関数」という意味です。
Listの検索処理などでよく使われます。
C#List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
int result = numbers.Find(n => n > 3);
Console.WriteLine(result); // 4
このn => n > 3は、条件を判定する匿名関数です。
5-4. delegate型に匿名関数を代入する方法
匿名関数は、delegate型の変数に代入できます。
まず、独自のdelegate型を定義してみます。
C#delegate int Calculate(int a, int b);
このdelegate型にラムダ式を代入できます。
C#Calculate add = (a, b) => a + b;
Console.WriteLine(add(10, 5)); // 15
匿名メソッドを代入することもできます。
C#Calculate subtract = delegate(int a, int b)
{
return a - b;
};
Console.WriteLine(subtract(10, 5)); // 5
このように、匿名関数はdelegate型と組み合わせて使います。
初心者のうちは、「Func、Action、Predicateはよく使うdelegate型」と理解しておけば十分です。
5-5. Func・Action・Predicateの使い分け
Func、Action、Predicateは、次のように使い分けます。
戻り値がある場合はFuncを使います。
C#Func<int, int> square = x => x * x;
戻り値がない場合はActionを使います。
C#Action<string> print = text => Console.WriteLine(text);
条件判定をしたい場合はPredicateを使います。
C#Predicate<int> isPositive = x => x > 0;
実際には、LINQではFuncが内部的によく使われます。たとえば、Whereの条件は「要素を受け取ってboolを返す関数」です。
C#var result = numbers.Where(n => n > 0);
このn => n > 0は、Func<int, bool>のような役割を持っています。
6. 匿名関数の実践例
6-1. 配列やListの要素を条件で絞り込む
匿名関数は、Listの要素を条件で絞り込むときによく使われます。
C#List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
var evenNumbers = numbers.Where(n => n % 2 == 0);
foreach (var number in evenNumbers)
{
Console.WriteLine(number);
}
このコードでは、n => n % 2 == 0というラムダ式で「偶数だけを取り出す」という条件を指定しています。
文字列のListでも使えます。
C#List<string> names = new List<string> { "Tanaka", "Sato", "Suzuki", "Yamada" };
var sNames = names.Where(name => name.StartsWith("S"));
foreach (var name in sNames)
{
Console.WriteLine(name);
}
このように、匿名関数を使うことで、条件をその場でわかりやすく書けます。
6-2. Listの要素を並び替える
匿名関数は、Listの並び替えにも使えます。
C#List<int> numbers = new List<int> { 5, 2, 8, 1, 3 };
numbers.Sort((a, b) => a.CompareTo(b));
foreach (var number in numbers)
{
Console.WriteLine(number);
}
このコードでは、(a, b) => a.CompareTo(b)という匿名関数で昇順に並び替えています。
降順にしたい場合は、次のように書けます。
C#numbers.Sort((a, b) => b.CompareTo(a));
オブジェクトのListを並び替える場合も便利です。
C#class User
{
public string Name { get; set; }
public int Age { get; set; }
}
List<User> users = new List<User>
{
new User { Name = "Tanaka", Age = 30 },
new User { Name = "Sato", Age = 25 },
new User { Name = "Suzuki", Age = 35 }
};
var sortedUsers = users.OrderBy(user => user.Age);
foreach (var user in sortedUsers)
{
Console.WriteLine($"{user.Name}: {user.Age}");
}
user => user.Ageによって、Ageプロパティを基準に並び替えています。
6-3. LINQのWhere・Selectで使う
C#の匿名関数は、LINQで非常によく使われます。
C#List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
var result = numbers
.Where(n => n >= 3)
.Select(n => n * 10);
foreach (var number in result)
{
Console.WriteLine(number);
}
このコードでは、まずWhereで3以上の数値を取り出し、Selectで10倍に変換しています。
処理の流れは次のようになります。
C#1, 2, 3, 4, 5
↓ Where(n => n >= 3)
3, 4, 5
↓ Select(n => n * 10)
30, 40, 50
LINQとラムダ式を組み合わせることで、配列やListの加工を簡潔に書けます。
6-4. イベント処理で使う
匿名関数は、ボタンのクリックなどのイベント処理でも使われます。
C#button.Click += (sender, e) =>
{
Console.WriteLine("ボタンがクリックされました");
};
このように、イベントが発生したときに実行したい処理を、その場で書けます。
WinFormsやWPFなどのGUIアプリケーションでは、イベント処理でラムダ式を使うことがあります。
例えば、クリック時にメッセージを表示する場合は次のように書けます。
C#button.Click += (sender, e) =>
{
MessageBox.Show("クリックされました");
};
イベント処理が短い場合はラムダ式で書くと便利ですが、処理が長くなる場合は別メソッドに分ける方が読みやすくなります。
6-5. コールバック処理で使う
コールバックとは、ある処理が終わったあとに呼び出される処理のことです。
匿名関数は、コールバック処理にもよく使われます。
C#void Execute(Action callback)
{
Console.WriteLine("処理を開始します");
callback();
Console.WriteLine("処理を終了します");
}
Execute(() =>
{
Console.WriteLine("コールバック処理です");
});
この例では、Executeメソッドに匿名関数を渡しています。
実行すると、次のような流れになります。
C#処理を開始します
コールバック処理です
処理を終了します
このように、匿名関数を使うと「あとで実行したい処理」を引数として渡せます。
6-6. 非同期処理で使う
C#では、非同期処理でも匿名関数を使うことがあります。
C#Func<Task> asyncAction = async () =>
{
await Task.Delay(1000);
Console.WriteLine("1秒後に実行されました");
};
await asyncAction();
async () =>のように書くことで、非同期の匿名関数を定義できます。
ボタンクリックなどのイベントで非同期処理を書く場合もあります。
C#button.Click += async (sender, e) =>
{
await Task.Delay(1000);
MessageBox.Show("非同期処理が完了しました");
};
非同期処理では、例外処理やキャンセル処理も重要です。実務では、try-catchを使ってエラーに備えることが大切です。
C#Func<Task> loadDataAsync = async () =>
{
try
{
await Task.Delay(1000);
Console.WriteLine("データを読み込みました");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
};
await loadDataAsync();
7. 匿名関数を使うメリット・デメリット
7-1. 匿名関数を使うメリット
匿名関数を使うメリットは、処理をその場で簡潔に書けることです。
通常のメソッドを作るほどではない短い処理を、必要な場所に直接書けます。
C#var adults = users.Where(user => user.Age >= 20);
このコードでは、「20歳以上のユーザーを取り出す」という条件をその場で書いています。
もし通常のメソッドに分けると、次のようになります。
C#static bool IsAdult(User user)
{
return user.Age >= 20;
}
var adults = users.Where(IsAdult);
もちろん、メソッドに分ける書き方も悪くありません。しかし、処理が短く一度しか使わない場合は、匿名関数の方が簡潔です。
7-2. コードを短く書ける
匿名関数、特にラムダ式を使うと、コードを短く書けます。
C#var result = numbers.Where(n => n > 10);
この1行だけで、「numbersの中から10より大きい要素を取り出す」という処理を表現できます。
匿名メソッドで書くと、次のようになります。
C#var result = numbers.Where(delegate(int n)
{
return n > 10;
});
ラムダ式の方が短く、読みやすいことがわかります。
7-3. 一時的な処理を簡潔に表現できる
匿名関数は、一時的に使う処理に向いています。
例えば、Listの並び替え条件を1回だけ指定したい場合です。
C#var sorted = products.OrderBy(product => product.Price);
このような処理のためだけに、わざわざ別メソッドを作る必要はありません。
一時的な条件、変換、イベント処理、コールバックなどは、匿名関数で書くとコードがすっきりします。
7-4. 匿名関数を使うデメリット
匿名関数は便利ですが、使いすぎるとコードが読みにくくなることがあります。
例えば、次のように処理が長いラムダ式は読みづらくなります。
C#var result = users.Where(user =>
{
if (user == null)
{
return false;
}
if (string.IsNullOrEmpty(user.Name))
{
return false;
}
if (user.Age < 20)
{
return false;
}
return true;
});
この程度ならまだ読めますが、条件がさらに増えると、何をしている処理なのか分かりにくくなります。
その場合は、通常のメソッドに分ける方がよいです。
C#static bool IsValidAdult(User user)
{
if (user == null)
{
return false;
}
if (string.IsNullOrEmpty(user.Name))
{
return false;
}
return user.Age >= 20;
}
そして、次のように使います。
C#var result = users.Where(IsValidAdult);
7-5. 複雑な処理では読みにくくなる
匿名関数は短い処理には向いていますが、複雑な処理には向いていない場合があります。
特に、次のような処理は通常のメソッドに分けることを検討しましょう。
・条件分岐が多い
・例外処理が必要
・処理が10行以上になる
・同じ処理を複数箇所で使う
・処理名を付けた方が意味が伝わりやすい
匿名関数は「短く書くためのもの」ではありますが、常に短く書けばよいわけではありません。
C#では、可読性を保つことが非常に大切です。読みやすさを優先して、匿名関数と通常メソッドを使い分けましょう。
8. 匿名関数でよく使うクロージャとは
8-1. クロージャとは
クロージャとは、匿名関数の外側にある変数を、匿名関数の内側で利用できる仕組みです。
例えば、次のコードを見てください。
C#int baseNumber = 10;
Func<int, int> addBase = x => x + baseNumber;
Console.WriteLine(addBase(5)); // 15
このコードでは、ラムダ式の外側にあるbaseNumberを、ラムダ式の中で使っています。
C#x => x + baseNumber
このように、匿名関数が外側の変数を参照する仕組みをクロージャと呼びます。
8-2. 外側の変数を匿名関数内で使う例
クロージャを使うと、外側の変数を利用して柔軟な処理を書けます。
C#int threshold = 60;
List<int> scores = new List<int> { 45, 70, 80, 55 };
var passedScores = scores.Where(score => score >= threshold);
foreach (var score in passedScores)
{
Console.WriteLine(score);
}
この例では、thresholdという外側の変数をラムダ式の中で使っています。
C#score => score >= threshold
thresholdの値を変えることで、条件を変更できます。
C#threshold = 75;
クロージャを使うと、匿名関数の外側で定義した設定値や条件を、匿名関数の中で利用できます。
8-3. クロージャを使うときの注意点
クロージャでは、外側の変数の「値」ではなく「変数そのもの」を参照している点に注意が必要です。
C#int count = 0;
Action increment = () =>
{
count++;
Console.WriteLine(count);
};
increment(); // 1
increment(); // 2
increment(); // 3
この例では、匿名関数の外側にあるcountが、匿名関数の中で更新されています。
これは便利ですが、意図しないタイミングで値が変わると、バグの原因になることがあります。
特に、複数の匿名関数が同じ外側の変数を参照している場合は注意しましょう。
8-4. ループ内で匿名関数を使うときの落とし穴
ループ内で匿名関数を使う場合、変数の参照に注意が必要です。
例えば、次のようなコードを考えます。
C#List<Action> actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
actions.Add(() => Console.WriteLine(i));
}
foreach (var action in actions)
{
action();
}
このコードでは、期待としては0, 1, 2が表示されると思うかもしれません。
しかし、ループ変数の扱いによっては、意図しない結果になることがあります。安全に書くには、ループ内で別の変数にコピーします。
C#List<Action> actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
int current = i;
actions.Add(() => Console.WriteLine(current));
}
foreach (var action in actions)
{
action();
}
このように、ループ内で匿名関数を使う場合は、現在の値を別の変数にコピーしてから使うと安全です。
9. 匿名関数を使うときの注意点
9-1. 処理が長くなりすぎないようにする
匿名関数は便利ですが、処理が長くなりすぎると読みづらくなります。
C#var result = users.Where(user =>
{
if (user == null)
{
return false;
}
if (user.Age < 20)
{
return false;
}
if (!user.IsActive)
{
return false;
}
return true;
});
このような処理がさらに長くなる場合は、通常のメソッドに分けることを検討しましょう。
C#static bool CanUseService(User user)
{
if (user == null)
{
return false;
}
if (user.Age < 20)
{
return false;
}
return user.IsActive;
}
そして、次のように呼び出します。
C#var result = users.Where(CanUseService);
メソッド名があることで、「何を判定しているのか」がわかりやすくなります。
9-2. 変数のスコープに注意する
匿名関数では、外側の変数を使えますが、スコープには注意が必要です。
C#void Sample()
{
int value = 10;
Action action = () =>
{
Console.WriteLine(value);
};
action();
}
この場合、valueはSampleメソッドの中で定義された変数ですが、匿名関数の中から参照できます。
ただし、外側の変数を使いすぎると、匿名関数がどの値に依存しているのか分かりにくくなります。
読みやすさを保つためには、匿名関数の中で使う外側の変数を必要最小限にすることが大切です。
9-3. nullや例外処理を考慮する
匿名関数の中でも、通常のメソッドと同じようにnullや例外処理を考慮する必要があります。
例えば、次のコードは注意が必要です。
C#var result = users.Where(user => user.Name.StartsWith("A"));
もしuserやuser.Nameがnullの場合、例外が発生します。
安全に書くなら、次のようにします。
C#var result = users.Where(user => user != null &&
user.Name != null &&
user.Name.StartsWith("A"));
または、null条件演算子を使うこともできます。
C#var result = users.Where(user => user?.Name?.StartsWith("A") == true);
匿名関数だからといって、例外が自動的に防がれるわけではありません。通常のコードと同じように安全性を考える必要があります。
9-4. デバッグしにくいケースに注意する
匿名関数はその場で処理を書ける反面、複雑になるとデバッグしにくくなることがあります。
特に、LINQの中で複数のラムダ式をつなげている場合、どの段階で意図しない値になっているのか分かりにくいことがあります。
C#var result = users
.Where(user => user.IsActive)
.Select(user => user.Name)
.Where(name => name.StartsWith("A"));
この程度なら問題ありませんが、条件や変換が複雑になる場合は、途中の結果を変数に分けると読みやすくなります。
C#var activeUsers = users.Where(user => user.IsActive);
var userNames = activeUsers.Select(user => user.Name);
var result = userNames.Where(name => name.StartsWith("A"));
デバッグしやすさを考えるなら、1つの式に詰め込みすぎないことが大切です。
9-5. 可読性を優先して通常メソッドに分ける判断基準
匿名関数を使うか、通常メソッドに分けるか迷ったときは、可読性を基準に判断しましょう。
次のような場合は、匿名関数のままで問題ありません。
C#var result = numbers.Where(n => n > 0);
処理が短く、意味がすぐに分かるからです。
一方、次のような場合はメソッドに分けた方がよいです。
C#var result = users.Where(user =>
{
if (user == null)
{
return false;
}
if (!user.IsActive)
{
return false;
}
if (user.Age < 20)
{
return false;
}
return user.HasPermission;
});
このような処理は、次のようにメソッド化すると意味が明確になります。
C#static bool CanAccess(User user)
{
if (user == null)
{
return false;
}
if (!user.IsActive)
{
return false;
}
if (user.Age < 20)
{
return false;
}
return user.HasPermission;
}
そして、呼び出し側はシンプルになります。
C#var result = users.Where(CanAccess);
匿名関数は便利ですが、「短い処理を読みやすく書くためのもの」と考えて使うとよいでしょう。
10. 匿名関数・ラムダ式でよくあるエラーと対処法
10-1. 型推論できないエラー
ラムダ式では、C#が引数や戻り値の型を推論してくれます。
C#Func<int, int> square = x => x * x;
この場合、xはint型だと推論されます。
しかし、代入先の型がない場合、C#はラムダ式の型を判断できません。
C#var square = x => x * x; // エラー
このコードは、xの型が分からないためエラーになります。
対処法は、Funcなどで型を明示することです。
C#Func<int, int> square = x => x * x;
または、引数の型を明示することもできます。
C#Func<int, int> square = (int x) => x * x;
ラムダ式は単独では型を持たず、delegate型や式木などの対象となる型が必要です。
10-2. 戻り値の型が合わないエラー
Funcを使う場合、戻り値の型が合っていないとエラーになります。
C#Func<int, int> getMessage = x => "値は" + x; // エラー
Func<int, int>は、intを受け取ってintを返す関数です。しかし、このラムダ式はstringを返しています。
正しくは、戻り値の型をstringにします。
C#Func<int, string> getMessage = x => "値は" + x;
複数行のラムダ式でも同じです。
C#Func<int, bool> isEven = x =>
{
return x % 2 == 0;
};
Func<int, bool>なので、戻り値はboolである必要があります。
10-3. delegate型が一致しないエラー
匿名関数をdelegate型に代入するときは、引数の数、引数の型、戻り値の型が一致している必要があります。
例えば、次のコードはエラーになります。
C#Action<int> print = (x, y) => Console.WriteLine(x + y); // エラー
Action<int>は、int型の引数を1つだけ受け取る処理です。しかし、ラムダ式側ではxとyの2つの引数を受け取っています。
正しくは、Action<int, int>にします。
C#Action<int, int> print = (x, y) => Console.WriteLine(x + y);
戻り値がある場合は、Funcを使います。
C#Func<int, int, int> add = (x, y) => x + y;
delegate型と匿名関数の形が一致しているかを確認することが大切です。
10-4. LINQで意図した結果にならないケース
LINQで匿名関数を使うときに、意図した結果にならないことがあります。
よくある原因の1つは、LINQの遅延実行です。
C#List<int> numbers = new List<int> { 1, 2, 3 };
var result = numbers.Where(n => n > 1);
numbers.Add(4);
foreach (var number in result)
{
Console.WriteLine(number);
}
この場合、resultを作った時点ではまだ処理が実行されていません。foreachで列挙したときに実行されるため、後から追加した4も結果に含まれます。
すぐに結果を確定したい場合は、ToList()を使います。
C#var result = numbers.Where(n => n > 1).ToList();
numbers.Add(4);
foreach (var number in result)
{
Console.WriteLine(number);
}
LINQでラムダ式を使うときは、遅延実行の仕組みに注意しましょう。
10-5. 初心者がつまずきやすいポイント
初心者がC#の匿名関数でつまずきやすいポイントは、主に次の3つです。
1つ目は、=>の意味が分かりにくいことです。
C#x => x * 2
これは、「xを受け取って、x * 2を返す関数」という意味です。
2つ目は、FuncやActionの型の読み方です。
C#Func<int, string>
これは、「intを受け取ってstringを返す関数」です。Funcでは最後の型が戻り値になります。
3つ目は、LINQとの組み合わせです。
C#numbers.Where(n => n > 10)
これは、「numbersの各要素nについて、n > 10を満たすものだけを取り出す」という意味です。
最初は難しく感じるかもしれませんが、Listの絞り込みや変換で何度も使っているうちに自然と読めるようになります。
11. C#の匿名関数に関するよくある質問
11-1. 匿名関数とラムダ式は同じですか?
厳密には、匿名関数とラムダ式は完全に同じ意味ではありません。
匿名関数は、名前のない関数全体を指す言葉です。その中に、匿名メソッドとラムダ式があります。
C#// 匿名メソッド
Action a = delegate()
{
Console.WriteLine("Hello");
};
// ラムダ式
Action b = () =>
{
Console.WriteLine("Hello");
};
どちらも匿名関数ですが、現在のC#ではラムダ式がよく使われます。
初心者のうちは、「ラムダ式は匿名関数の代表的な書き方」と理解しておくとよいでしょう。
11-2. 匿名関数はいつ使えばよいですか?
匿名関数は、短い処理をその場で書きたいときに使うと便利です。
例えば、Listを絞り込む条件を書く場合です。
C#var adults = users.Where(user => user.Age >= 20);
また、イベント処理やコールバック処理でも使えます。
C#button.Click += (sender, e) =>
{
Console.WriteLine("クリックされました");
};
ただし、処理が長い場合や何度も使い回す場合は、通常のメソッドに分けた方が読みやすくなります。
11-3. delegateを理解しないと匿名関数は使えませんか?
匿名関数を使い始めるだけなら、delegateを完全に理解していなくても問題ありません。
まずは、Func、Action、Predicateを覚えるとよいです。
C#Func<int, int> square = x => x * x;
Action<string> print = text => Console.WriteLine(text);
Predicate<int> isEven = x => x % 2 == 0;
ただし、匿名関数はdelegate型に代入されたり、メソッドの引数として渡されたりします。
そのため、C#を深く理解するには、最終的にはdelegateの基本も学んでおくとよいでしょう。
11-4. 匿名関数はパフォーマンスに影響しますか?
匿名関数は便利ですが、使い方によってはパフォーマンスに影響する場合があります。
特に、クロージャによって外側の変数を参照する場合、内部的に追加のオブジェクトが作られることがあります。
C#int value = 10;
Func<int, int> add = x => x + value;
このようなコードでは、外側の変数valueを匿名関数が参照しています。
通常のアプリケーションでは大きな問題になることは少ないですが、大量に繰り返される処理や、高いパフォーマンスが求められる処理では注意が必要です。
まずは可読性を優先し、必要に応じてパフォーマンスを測定して改善するのが現実的です。
11-5. 初心者は匿名メソッドとラムダ式のどちらから学ぶべきですか?
初心者は、まずラムダ式から学ぶのがおすすめです。
理由は、現在のC#ではラムダ式が圧倒的によく使われるからです。
C#var result = numbers.Where(n => n > 0);
このようなコードは、C#の実務や学習サイト、サンプルコードで頻繁に登場します。
匿名メソッドは、古い書き方や既存コードを読むために知っておく程度で問題ありません。
学習の流れとしては、次の順番がおすすめです。
ラムダ式の基本構文を覚える
FuncとActionを覚える
LINQのWhereやSelectで使ってみる
クロージャの注意点を理解する
匿名メソッドも読めるようにする
この順番で学ぶと、C#の匿名関数を無理なく理解できます。
まとめ
C#の匿名関数とは、名前を付けずにその場で定義できる関数のことです。匿名関数には、delegateキーワードを使う匿名メソッドと、=>を使うラムダ式があります。
現在のC#では、ラムダ式がよく使われます。
C#Func<int, int> square = x => x * x;
ラムダ式を使うと、Listの絞り込みや変換、LINQ、イベント処理、コールバック処理などを簡潔に書けます。
C#var evenNumbers = numbers.Where(n => n % 2 == 0);
また、戻り値がある処理にはFunc、戻り値がない処理にはAction、条件判定にはPredicateを使うと理解しやすくなります。
匿名関数は便利ですが、処理が長くなりすぎると読みにくくなります。短い処理や一時的な処理には匿名関数を使い、複雑な処理や再利用したい処理は通常のメソッドに分けることが大切です。
C#でLINQやイベント処理を使いこなすためには、匿名関数とラムダ式の理解が欠かせません。まずは、x => x * 2のようなシンプルなラムダ式から練習し、少しずつFunc、Action、Predicate、クロージャへと学習を広げていきましょう。

