C# 7.0 で導入された「ローカル関数」を使用すると、メソッドの内部に、そのメソッド専用の関数を定義することができます。
従来、特定のメソッドからしか呼ばれない小さなヘルパーメソッドであっても、クラスの private メソッドとして定義する必要があり、クラス全体の可読性を下げる要因となっていました。ローカル関数を利用することで、**「その処理を使う場所の直近」**にロジックを配置でき、カプセル化の促進とコードの見通しを良くすることが可能です。
本記事では、文字列配列をスネークケースに変換する処理を例に、ローカル関数の定義と利用方法を解説します。
実装例:スネークケース変換処理
以下のコードは、パスカルケース(PascalCase)で書かれた単語のリストを、スネークケース(snake_case)に変換して出力するプログラムです。変換ロジックである ToSnakeCase 関数は Main メソッドの内部でのみ使用されるため、ローカル関数として定義しています。
using System;
using System.Linq;
namespace LocalFunctionExample
{
class Program
{
static void Main(string[] args)
{
// 変換対象の文字列配列
var words = new[]
{
"StringBuilder",
"ToSnakeCase",
"IsLetter",
"ToLower"
};
Console.WriteLine("--- 変換結果 ---");
foreach (var word in words)
{
// ローカル関数を呼び出し
var snakeCase = ToSnakeCase(word);
Console.WriteLine($"{word} -> {snakeCase}");
}
// --- ここにローカル関数を定義 ---
// パスカルケースをスネークケースに変換するローカル関数
// この関数は Main メソッドの外からは見えない
string ToSnakeCase(string value)
{
if (string.IsNullOrEmpty(value)) return value;
// 2文字目以降の大文字の前に "_" を挿入する処理
var convertedSequence = value.Select((c, i) =>
(i > 0 && char.IsUpper(c)) ? "_" + c : c.ToString()
);
// 文字列を結合して小文字化
return string.Concat(convertedSequence).ToLower();
}
}
}
}
ローカル関数の特徴とメリット
1. スコープの限定(汚染防止)
ローカル関数は定義されたメソッド(親メソッド)の内部からしか呼び出せません。これにより、「このメソッドはどこから使われているのか?」とクラス全体を探し回る必要がなくなり、コードの依存関係が明確になります。
2. 親メソッドの変数へのアクセス(クロージャ)
ローカル関数は、親メソッドで定義された変数や引数に直接アクセス(キャプチャ)することができます。これにより、引数としてわざわざ値を渡す手間を省くことができます。
void ProcessData(int threshold)
{
var numbers = new[] { 1, 5, 10, 2 };
// 親メソッドの引数 threshold を直接使用できる
var filtered = numbers.Where(IsOverThreshold);
bool IsOverThreshold(int n) => n > threshold;
}
3. イテレータや非同期メソッドでの例外処理の即時化
yield return を使うイテレータメソッドや async/await メソッドでは、引数の検証(nullチェックなど)を即座に行い、実処理をローカル関数に委譲するパターンがよく使われます。これにより、遅延実行される処理の前に確実にエラーを検出できます。
まとめ
ローカル関数は、コードの凝集度を高めるための強力な機能です。
- 使用場所: ヘルパーメソッドが特定のメソッドからしか参照されない場合。
- 記述位置: 親メソッド内の任意の位置(一般的にはメソッドの末尾、または使用する直前)。
- 効果: クラスレベルの名前空間を汚さず、ロジックを整理できる。
単なるコードの整理だけでなく、変数のキャプチャなどの機能も活用し、意図が伝わりやすいコード記述に役立ててください。
