特集
» 2017年07月19日 05時00分 UPDATE

特集:C# 7の新機能詳説:第1回 明瞭なコーディングのために (3/3)

[山本康彦,BluewaterSoft/Microsoft MVP for Windows Development]
前のページへ 1|2|3       

ローカル関数を置けるところ

 ローカル関数は、以下に示すようにおよそ文を書けるところならどこにでも置ける。

  • メソッド
  • コンストラクタ/ファイナライザ
  • プロパティのアクセサ/イベントのアクセサ
  • 匿名メソッド/ラムダ式(ステートメント形式のもの)
  • ローカル関数

 プロパティの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; }

プロパティの中にローカル関数を置く例(C# 7)
このNumberプロパティのsetterは、代入された値を_numberメンバ変数に格納するとともに、それが偶数かどうかを判定してIsEvenプロパティに設定している。その偶数かどうかを判定する部分を、ローカル関数IsEvenNumberにくくり出した。

ここでは、ローカル関数をラムダ形式ではなく通常のメソッドの形式で記述した。


イテレーターメソッドの例外発生タイミングを改善する

 ローカル関数のちょっと面白い効果として、例外の発生するタイミングを変えられる場合がある。

 例として次のコードのようなイテレーターメソッドを考えてみよう。

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メソッドは、引数に与えられた範囲の整数から偶数だけを取り出して列挙する。ただし、引数の範囲を間違えて呼び出すと、ArgumentOutOfRangeExceptionが発生する。

 上のRangeEven1メソッドは、冒頭で引数チェックを行っている。引数を間違えたときは、RangeEven1メソッドを呼び出したところで例外が出ると期待される。

 ところが実際に呼び出してみると(次のコード)、例外が発生するのはRangeEven1メソッドを呼び出した行ではなく、RangeEven1メソッドが返してきたオブジェクトを使っている行である。イテレーターメソッドは遅延実行されるため、このような現象が起きるのだ。

try
{
  IEnumerable<int> evens = RangeEven1(10, 1);
  WriteLine(string.Join(", ", evens.Select(n => n.ToString()))); // ←ここで例外
}
catch(Exception ex){ /* ……省略…… */ }

イテレーターメソッドの引数エラー(ローカル関数未使用)
引数を間違えてRangeEven1メソッドを呼び出したのだが、呼び出した行では例外が発生せず、その次の行で遅延実行を開始したときに例外が出る。これでは、Select拡張メソッドの中で例外が出ているかのように思えてしまう。

引数チェックの例外は、メソッドを呼び出したときに出てほしいものだ。


 従来はこれを改良するために、引数チェック部分と実際に列挙する部分を分離して、後者を別のメソッドに切り出していた。しかしそうすると、切り出したメソッド(=引数チェックなしで列挙だけ行うメソッド)をクラス内から無防備に使ってしまうというミスを誘発しかねない。

 ローカル関数を使えば、そんなミスの可能性をなくしつつ、例外の発生タイミングをメソッド呼び出し時にできる(次のコード)。

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)
実際に列挙する部分(イテレーターブロック)だけをローカル関数Innerに切り出すことによって、引数チェックでの例外が発生するタイミングがRangeEven2メソッドを呼び出すところに変わった。

まとめ

 今回は、C# 7の新機能の中から、明瞭なコーディングに役立つ2つの新機能の使い方を紹介した。新しい数値リテラル構文は、数値リテラルの読み間違いを防ぐのに役立つ。ローカル関数は、メソッドのスコープを明確にできる。

「特集:C# 7の新機能詳説」のインデックス

特集:C# 7の新機能詳説

前のページへ 1|2|3       

Copyright© 1999-2017 Digital Advantage Corp. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。