ローカル関数は、以下に示すようにおよそ文を書けるところならどこにでも置ける。
プロパティのsetterに置く例を次のコードに示す。
private int _number;
public int Number
{
get => _number;
set
{
_number = value;
IsEven = IsEvenNumber();
// ローカル関数はプロパティにも置ける
bool IsEvenNumber()
{
return (value % 2) == 0;
}
}
}
public bool IsEven { get; private set; }
ここでは、ローカル関数をラムダ形式ではなく通常のメソッドの形式で記述した。
ローカル関数のちょっと面白い効果として、例外の発生するタイミングを変えられる場合がある。
例として次のコードのようなイテレーターメソッドを考えてみよう。
private static IEnumerable<int> RangeEven1(int start, int end)
{
if (start > end)
throw new ArgumentOutOfRangeException();
for (int i = start; i <= end; i++)
if (i % 2 == 0)
yield return i;
}
上のRangeEven1メソッドは、冒頭で引数チェックを行っている。引数を間違えたときは、RangeEven1メソッドを呼び出したところで例外が出ると期待される。
ところが実際に呼び出してみると(次のコード)、例外が発生するのはRangeEven1メソッドを呼び出した行ではなく、RangeEven1メソッドが返してきたオブジェクトを使っている行である。イテレーターメソッドは遅延実行されるため、このような現象が起きるのだ。
try
{
IEnumerable<int> evens = RangeEven1(10, 1);
WriteLine(string.Join(", ", evens.Select(n => n.ToString()))); // ←ここで例外
}
catch(Exception ex){ /* ……省略…… */ }
引数チェックの例外は、メソッドを呼び出したときに出てほしいものだ。
従来はこれを改良するために、引数チェック部分と実際に列挙する部分を分離して、後者を別のメソッドに切り出していた。しかしそうすると、切り出したメソッド(=引数チェックなしで列挙だけ行うメソッド)をクラス内から無防備に使ってしまうというミスを誘発しかねない。
ローカル関数を使えば、そんなミスの可能性をなくしつつ、例外の発生タイミングをメソッド呼び出し時にできる(次のコード)。
private static IEnumerable<int> RangeEven2(int start, int end)
{
if (start > end)
throw new ArgumentOutOfRangeException();
return Inner();
IEnumerable<int> Inner()
{
for (int i = start; i <= end; i++)
if (i % 2 == 0)
yield return i;
}
}
……省略……
// RangeEven2メソッドの呼び出し
try
{
IEnumerable<int> evens = RangeEven2(10, 1); // ←ここで例外
WriteLine(string.Join(", ", evens.Select(n => n.ToString())));
}
catch(Exception ex){ /* ……省略…… */ }
今回は、C# 7の新機能の中から、明瞭なコーディングに役立つ2つの新機能の使い方を紹介した。新しい数値リテラル構文は、数値リテラルの読み間違いを防ぐのに役立つ。ローカル関数は、メソッドのスコープを明確にできる。
Copyright© Digital Advantage Corp. All Rights Reserved.