7の新機能「ローカル関数」はどんなときに、どのように使えばよいのか。その基本的な使い方や、副次的なメリット、ラムダ式との違いなどを解説する。2017)の新機能にローカル関数がある。本稿では、その使いどころを紹介する。ローカル関数の使いどころローカル関数の使いどころまとめ特定のトピックをすぐに知りたいという方は以下のリンクを活用してほしい。2017以降が必要である。サンプルコードはコンソールアプリの一部であり、コードの冒頭に以下の宣言が必要となる。System.Console;本稿のサンプルコードに必要な宣言(C#)ローカル関数の使いどころもちろんローカル関数が書けるところならどのように使ってもよいのだが、使うべき場面の筆頭は「隠蔽(いんぺい)したいとき」である。話を進める前に変数について考えてみよう。C#では、あるクラスのメンバ変数だけを使ってもプログラミングは可能なのに、なぜローカル変数を使うのだろう。それは、「変数のスコープをメソッド内(あるいはブロック内)に限定する」ことで、分かりやすく、「内部的にしか使用しない変数の値を必要以上に外部に公開することがない」コードになるからだ。これと同じように、ローカル関数のスコープはその親メソッド内(あるいはブロック内)に限定されるので、分かりやすく、内部的にしか行わない処理を必要以上に外部に公開しないコードになるはずである。例としてローカル関数を使っていない次のようなコードを考えてみよう。このIsMultipleOfメソッドを呼び出しているのは、FizzBuzzメソッドだけであるとする。IsMultipleOfメソッドはクラス内のどこからでも呼び出せるので、(将来の)安全を期して引数チェックのコードを書いている。0;}従来の書き方(C#)このIsMultipleOfメソッドを、FizzBuzzメソッドのローカル関数にしてみる(次のコード)。FizzBuzzメソッド以外から呼び出される心配はなくなったので、安心して引数チェックを省略できる。副次的な効果として引数が1つ不要にもなっている。returnするメソッドやasync/awaitを使うメソッドで分かりやすい例外を出すといった用途もある(後述する)。ローカル関数の書き方ローカル関数の書き方は、従来のメソッドとだいたい同じだ。次のような相違点がある。アクセス修飾子を付けられない:親のメソッドなどからしかアクセスできないので、publicやprotectedなどのアクセス修飾子を付ける意味がないstaticキーワードを付けられない:親のメソッドなどと同じになる属性を付けられないローカル関数の記述は、メソッドだけでなくブロックを持っているメンバならどこにでも書ける。他のローカル関数ローカル関数は、ローカル変数とは違って、定義する前でも参照できる(次のコード)。ローカル変数のスコープは宣言箇所からブロック末尾までだが、ローカル関数のスコープはブロック全体なのである。n;}ローカル関数は定義の前でも参照できる(C#)LocalFunc02ローカル関数は、その定義より前で呼び出しているが問題なく動作する。ローカル関数を末尾に置く場合、このように短いローカル関数なら見通しは悪くならない。長いローカル関数を末尾に置くときは、その直前にreturnステートメントを目立つように置くとよいだろう。それ以降の記述はローカル関数だけであると示すためだ。この例に示したように、(通常のメソッドなどと同じく)ローカル関数の本体をラムダ式で記述することもできる。ローカル関数を定義した位置から見える変数やメソッドなどに、ローカル関数内からアクセスできる(次のコード)。ラムダ式や匿名関数とも共通する性質であるが、このようなものをクロージャとも呼ぶ。p;}ローカル関数内部から親メソッドのローカル変数にアクセスする(C#)また、ローカル関数は、通常のメソッドと同様にデリゲートとしても扱える(次のコード)。出力:evens=2,4,6,8,10}ローカル関数をデリゲートとして扱う(C#)隠蔽するためにこれまでならラムダ式を使ってデリゲートを定義していたような場面で、ローカル関数が使える。15.6.4では、上のコードのIsOddの定義部分で、ローカル関数を使うべきだという「電球」アイコンの警告が出た(次の画像)。デリゲートの定義にもローカル関数が推奨されているラムダ式では難しかったこと直前のサンプルコードなどを見ると、ローカル関数とラムダ式(あるいは匿名関数)は同じようなものに思えるかもしれない。ブロックだけでなく式の中にも書けるラムダ式の方が便利そうな気もするだろう。ローカル関数は、ラムダ式では難しかったりできなかったりした次のようなことも可能なのだ。returnするメソッド)が書けないラムダ式では、引数の既定値を与えられない(省略可能な引数にできない)ラムダ式では、ジェネリックにできないiteratormethods#yieldメソッドでの事前チェックローカル関数の使いどころとして、隠蔽以外に副次的なメリットがあるケースを2つ紹介しよう。いずれも例外の出し方を分かりやすくするものだ。returnで結果を順に返していくメソッドだ。イテレータメソッドの先頭で引数をチェックして、範囲外なら例外を出すものとしよう。従来の書き方では、範囲外の引数を渡したとき、例外が出るのは列挙を始めるときになってしまう(次のコード)。DEBUGReadKey();#endif}従来の書き方で引数チェックするイテレータメソッドを使う(C#)このEvensメソッドは、冒頭で引数をチェックしているので、呼び出したときに(コード中「Evens(-1)」のところで)例外が出てほしい。実際には、その後のforeachループで列挙を始めるときに例外が出る。returnする部分を分離すれば、期待通りにメソッド呼び出し時に例外が出るようになる(次のコード)。returnする部分をローカル関数にして、引数をチェックする部分と分離している。ローカル関数を呼び出す前に引数チェックが実行されるので、範囲外の引数を渡されたときにはOddsメソッドを呼び出した時点で例外が出る。asyncメソッドでの事前チェックローカル関数を使って例外の出し方を分かりやすくするケースの2つ目は非同期メソッド、つまりシグネチャにasyncが付いているメソッドだ。非同期メソッドの先頭で引数をチェックして、範囲外なら例外を出すものとしよう。従来の書き方で非同期実行の終了を待機した場合、発生した例外はAggregateException例外(System名前空間)にラップされ、待機中に補足される(次のコード)。実際に発生した例外を知るにはそのInnerExceptionsプロパティの内容を列挙しなければならず、少々面倒なのだ。することはできません。DEBUGReadKey();#endif}従来の書き方で引数チェックする非同期メソッドを使う(C#)このGetWebPageTitleAsync1メソッドは、冒頭で引数をチェックしてArgumentNullException例外(System名前空間)を発生させている。非同期実行を始める前なのだから、AggregateException例外にラップされることなく、即座にArgumentNullException例外のまま送出されてほしい。実際にはラップされてしまい、非同期実行の待機中になってから出てくる。キャッチした側ではAggregateException例外のInnerExceptionsプロパティを列挙しなければならない。TIPS「正規表現を使ってパターンに一致する全ての文字列を抽出するには?[C#/VB]」を参照のこと。これも、ローカル関数を使って引数チェックと非同期実行する部分を分離すれば、期待通りにArgumentNullException例外がそのまま即座に出てくるようになる(次のコード)。することはできません。DEBUGReadKey();#endif}ローカル関数を使った書き方で引数チェックする非同期メソッドを使う(C#)GetWebPageTitleAsync2メソッドの中で非同期実行する部分をローカル関数にして、引数をチェックする部分と分離している。それによって、GetWebPageTitleAsync2メソッド自体は非同期メソッドでなくせる(シグネチャからasyncを外せる)。asyncを付けない普通のメソッドになったので、引数チェックで投げた例外はそのまま即座に送出される。ローカル関数を呼び出すところにawaitを付けてしまうと(=GetWebPageTitleAsync2メソッド自体にasyncを付けたままだと)、元のコードと同様にAggregateException例外にラップされてしまうので気を付けよう。まとめローカル関数の典型的な使いどころは、隠蔽したいときだ。メソッドなどから一部のコードをメソッドとして切り出したいのだが、しかし他のメソッドなどからはアクセスさせたくないという場合である。メソッドなどの中でデリゲートを定義する代わりとしても推奨される。メールマガジンをご購読ください。申し込みページへITmediaはアイティメディア株式会社の登録商標です。