C#のローカル関数とは?使い方やメリット・ラムダ式との違いも紹介

エンジニア
マネージャー
C#のローカル関数とは?
今回は、C#のローカル関数について説明します。ローカル関数とは、関数の中に記述する関数のことで、C# 7.0から追加された機能です。
ローカル関数の使い方やメリットについて説明します。興味のある方はぜひご覧ください。
C#のローカル関数のメリット
C#ローカル関数のメリットとしては、以下の内容があげられます。
・内部的にしか使用しない変数の値を隠蔽可能
・変数のスコープが関数内に限定されるので、プログラマが読みやすい
・親関数で引数チェックされていれば、改めての引数チェックは不要(コードがシンプルになる)
・親関数の変数を使用できるので、ローカル関数の引数が少なくなる(コードがシンプルになる)
・メソッド反復子や非同期メソッドで例外を直ちに検出できる(後述します)
C#のローカル関数の基本的な構文
C#のローカル変数の基本的な書き方は以下になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
static int Func1(int a) { // 引数チェック if (a < 0) return 0; int Func2(int b) { return a - b; } System.Console.WriteLine(Func2(1)); return 1; } |
ローカル関数を呼び出すことができる要素
C#のローカル関数は関数だけでなく以下のようなブロックを持つメンバにも記述可能です。
・コンストラクタ
・プロパティ アクセサ
・イベント アクセサ
・ラムダ式
・ファイナライザ
・その他のローカル関数
ローカル関数と通常の関数との違い
C#のローカル関数の書き方は、通常の関数とほとんど同じですが次のような違いがあります。
・staticキーワードは付与できない
・アクセス修飾子を付与できない
・属性を付与できない
また、ローカル変数とは違い、定義前でも参照できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
static int Func1(int a) { // 引数チェック if (a < 0) return 0; // Func3の定義は下に記述している System.Console.WriteLine(Func3(1)); int Func3(int b) { return a - b; } return 1; } |
C#のローカル関数の使い方
ここからはC#のローカル関数の例外発生タイミングとデリゲートでC#のローカル関数を使った時の動きについて紹介します。紹介したプログラムを実際に動かしてみるとより理解が深まります。
例外を検出する
メソッド反復子、非同期メソッドで例外が発生するタイミングは実際に例外になる処理が実行されるときになります。しかしC#のローカル関数を使った場合、それぞれ反復子の取得時、非同期メソッド実行前に発生するようになります。
メソッド反復子を使う
メソッド反復子の場合、例外は、反復子を取得した時点では検出されず、シーケンスを列挙する時点で検出されます。
実際のソースコードを見てみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
using System; using System.Collections.Generic; public class Program { static void Main(string[] args) { IEnumerable<int> val1 = GetIntListYeild(3); IEnumerable<int> val2 = GetIntListYeild(-1); // ここでは例外が検出されない try { foreach (var i in val1) { Console.WriteLine(i.ToString()); } foreach (var i in val2) // ここで例外が検出される { Console.WriteLine(i.ToString()); } } catch (Exception e) { Console.WriteLine(e.GetType()); } } static IEnumerable<int> GetIntListYeild(int max) { // 引数チェック if (max < 1) { throw new ArgumentOutOfRangeException(); } for (int i = 0; i < max; i++) { yield return i; } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
0 1 2 System.ArgumentOutOfRangeException </code><pre> 例外は、反復子を取得した時点では検出されず、シーケンスを列挙する時点で検出されることが分かります。 これに対して、C#のローカル関数にすると、反復子を取得した時点で例外を検出できます。 <pre> using System; using System.Collections.Generic; public class Program { static void Main(string[] args) { IEnumerable<int> val1 = null; IEnumerable<int> val2 = null; try { val1 = GetIntListYeild(3); val2 = GetIntListYeild(-1); // ここで例外が検出される } catch (Exception e) { Console.WriteLine(e.GetType()); } if (val1 != null) { foreach (var i in val1) { Console.WriteLine(i.ToString()); } } if (val2 != null) { foreach (var i in val2) { Console.WriteLine(i.ToString()); } } } static IEnumerable<int> GetIntListYeild(int max) { // 引数チェック if (max < 1) { throw new ArgumentOutOfRangeException(); } return LocalFunc(); IEnumerable<int> LocalFunc() { for (int i = 0; i < max; i++) { yield return i; } } } } |
1 2 3 4 5 6 |
System.ArgumentOutOfRangeException 0 1 2 |
反復子を取得した時点で例外が検出されることが分かります。
非同期メソッドを使う
非同期メソッドの場合、例外はAggregateExceptionにラップされ、タスク待機中に検出されます。
実際のソースコードを見てみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
using System; using System.Threading.Tasks; public class Program { static void Main(string[] args) { Task<int> task1 = AsyncFunc(3); Task<int> task2 = AsyncFunc(-1); // ここでは例外が検出されない try { task1.Wait(); int result1 = task1.Result; Console.WriteLine(result1); task2.Wait(); // ここで例外が検出される int result2 = task2.Result; Console.WriteLine(result2); } catch (Exception e) { Console.WriteLine(e.GetType()); } } static async Task<int> AsyncFunc(int secondsDelay) { // 引数チェック if (secondsDelay < 0 || secondsDelay > 5) { throw new ArgumentOutOfRangeException(); } // 待ち合わせ await Task.Delay(secondsDelay * 1000); return 0; } } |
実行結果は以下のようになります。
1 2 3 4 |
0 System.AggregateException |
これに対して、C#のローカル関数にすると非同期メソッドを呼び出す前に例外を検出できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
using System; using System.Threading.Tasks; public class Program { static void Main(string[] args) { Task<int> task1 = null; Task<int> task2 = null; try { task1 = AsyncFunc(3); task2 = AsyncFunc(-1); // ここで例外が検出される } catch (Exception e) { Console.WriteLine(e.GetType()); } if (task1 != null) { task1.Wait(); int result1 = task1.Result; Console.WriteLine(result1); } if (task2 != null) { task2.Wait(); int result2 = task2.Result; Console.WriteLine(result2); } } static Task<int> AsyncFunc(int secondsDelay) { // 引数チェック if (secondsDelay < 0 || secondsDelay > 5) { throw new ArgumentOutOfRangeException(); } return LocalFunc(); async Task<int> LocalFunc() { // 待ち合わせ await Task.Delay(secondsDelay * 1000); return 0; } } } |
1 2 3 4 |
System.ArgumentOutOfRangeException 0 |
また、例外はAggregateExceptionにラップされることもありません。
デリゲートとして扱う
C#のローカル関数は、通常メソッドと同じようにデリゲートとしても使用できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
using System; using System.Collections.Generic; public class Program { static void Main(string[] args) { // デリゲートで使用するローカル関数定義 bool IsEven(int n) { return n % 2 == 0; } // 実行 var evens = Enumerable.Range(1, 10).Where(IsEven); WriteLine($"evens={string.Join(",", evens)}"); } } |
実行結果は以下のようになります。
1 2 3 |
evens=2,4,6,8,10 |
C#のローカル関数とラムダ式の違い
C#のローカル関数はラムダ式と似ていますが、以下のようにラムダ式とは異なる特徴があります。
・関数名が必要
・引数の型を指定できる
・引数に既定値を与えられる
・イテレータ(yield return ステートメント)が使える
実際のコードを見てみましょう。
ローカル関数を使用したサンプルコード
C#のローカル関数では引数の型を宣言することができます。
1 2 3 4 5 6 7 8 9 10 11 |
public static int Func5(int n) { return inFunc5(n); int inFunc5(int num) { return num < 2 ? 1 : num * inFunc5(num - 1); } } |
ラムダ式を使用したサンプルコード
一方、ラムダ式は引数の型を指定できないため、使用時の型に依存した動きになります。
1 2 3 4 5 6 7 8 9 10 |
public static int Func6(int n) { Func<int, int> inFunc6 = default(Func<int, int>); inFunc6 = num => num < 2 ? 1 : num * inFunc6(num - 1); return inFunc6(n); } |
エンジニア
マネージャー
C#のローカル関数について知ろう
いかがでしたでしょうか。C#のローカル変数には可読性の向上のほか、ラムダ式では実現できなかったイテレータの使用など様々な使い方ができます。
実際にコードを実行して理解を深め、使用すべきところを検討してください。
FEnet.NETナビ・.NETコラムは株式会社オープンアップシステムが運営しています。
株式会社オープンアップシステムはこんな会社です
秋葉原オフィスには株式会社オープンアップシステムをはじめグループのIT企業が集結!
数多くのエンジニアが集まります。

-
スマホアプリから業務系システムまで
スマホアプリから業務系システムまで開発案件多数。システムエンジニア・プログラマーとしての多彩なキャリアパスがあります。
-
充実した研修制度
毎年、IT技術のトレンドや社員の要望に合わせて、カリキュラムを刷新し展開しています。社内講師の丁寧なサポートを受けながら、自分のペースで学ぶことができます。
-
資格取得を応援
スキルアップしたい社員を応援するために資格取得一時金制度を設けています。受験料(実費)と合わせて資格レベルに合わせた最大10万円の一時金も支給しています。
-
東証プライム上場企業グループ
オープンアップシステムは東証プライム上場「株式会社オープンアップグループ」のグループ企業です。
安定した経営基盤とグループ間のスムーズな連携でコロナ禍でも安定した雇用を実現させています。
株式会社オープンアップシステムに興味を持った方へ
株式会社オープンアップシステムでは、開発系エンジニア・プログラマを募集しています。
年収をアップしたい!スキルアップしたい!大手の上流案件にチャレンジしたい!
まずは話だけでも聞いてみたい場合もOK。お気軽にご登録ください。


C#新着案件New Job
システム開発/東京都新宿区/【WEB面談可/C#経験者/20代前半の方活躍中/経験1年以上の方活躍中】/在宅勤務
月給29万~34万円東京都新宿区(新宿駅)システム開発/東京都新宿区/【WEB面談可/C#経験者/20代後半~40代の方活躍中/経験年数不問】/在宅勤務
月給41万~50万円東京都新宿区(新宿駅)デバック、テスト項目の作成/神奈川県横浜市/【WEB面談可/C#経験者/20代前半の方活躍中/経験1年以上の方活躍中】/在宅勤務
月給29万~34万円神奈川県横浜市(桜木町駅)デバック、テスト項目の作成/神奈川県横浜市/【WEB面談可/C#経験者/20代後半~40代の方活躍中/経験年数不問】/在宅勤務
月給41万~50万円神奈川県横浜市(桜木町駅)基幹システム開発導入/東京都新宿区/【WEB面談可/C#経験者/20代前半の方活躍中/経験1年以上の方活躍中】/在宅勤務
月給29万~34万円東京都新宿区(西新宿駅)基幹システム開発導入/東京都新宿区/【WEB面談可/C#経験者/20代後半~40代の方活躍中/経験年数不問】/在宅勤務
月給41万~50万円東京都新宿区(西新宿駅)