C#14新機能

C#14 新機能 B06_拡張プロパティ

今回は「拡張プロパティ」について見ていきます。extension ブロックには、拡張メソッドをグルーピングして整理できるという側面に加えて、通常のクラスと同じようにプロパティを書けるようになったという側面があります。これまでの拡張メソッドは、名前のとおりメソッドしか書けませんでしたが、extension ブロックの登場によってプロパティも書けるようになりました。

これまでの拡張メソッド

まずは int 型を拡張する extension ブロックに、IsZero と IsNegative という2つのメソッドが定義されている状態です。どちらも bool を返すだけの、値を判定するメソッドです。

図1 IsZero・IsNegative を通常のメソッドとして定義した状態

public static class Ex
{
  extension(int value)
  {
    public bool IsZero()
    {
      return value == 0;
    }

    public bool IsNegative()
    {
      return value < 0;
    }
  }
}

メソッドをプロパティに書き換える

IsZero は、ただ値を返すだけで引数も取らないので、メソッドというよりプロパティとして書くほうが自然です。まず () を外すと、get アクセサーがない状態になりエラーになります。

そこで get { } を追加し、その中に return value == 0; を移動させることで、IsZero を正しいプロパティとして定義できます。

図2 () を外して get アクセサーを追加している途中の状態

図3 get アクセサーを持つプロパティとして完成した IsZero

プロパティの中身は return value == 0; だけなので、次のように => を使ったシンプルな書き方に整理できます。

図4 式形式(=>)で簡潔に書き直した IsZero プロパティ

public static class Ex
{
  extension(int value)
  {
    public bool IsZero => value == 0;

    public bool IsNegative()
    {
      return value < 0;
    }
  }
}

プロパティとして使う

IsZero をプロパティにしたことで、呼び出し方も変わります。a.IsZero() のように () を付けて呼び出すとエラーになり、a.IsZero と () なしで呼び出すのが正しい書き方になります。

図5 a.IsZero() と () を付けて呼び出そうとしてエラーになっている状態

図6 a.IsZero と () なしで呼び出す、正しい使い方

if 文の条件式で使う場合も、if (a.IsZero) のように書けます。ただ bool を返すだけの判定にメソッドの () が付いていると違和感がありますが、プロパティであれば自然な書き方になります。

int a = -1;
bool ret1 = a.IsZero;

if (a.IsZero)
{
  ret1 = false;
}

int b = 0;
bool ret2 = b.IsNegative();

既存の拡張メソッドとの違い

ちなみに IEnumerable<T> の Any() や Count() には () が付いています。これらも中身は拡張メソッドで、実装を見ると this IEnumerable<TSource> source という形でメソッドとして定義されています。

図7 System.Linq.Enumerable の Any メソッドの定義。this を使った拡張メソッドとして実装されている

Any() が作られた当時は extension ブロックが存在しなかったため、拡張メソッドとしてはメソッドしか書けませんでした。そのため IEnumerable<T> の IsEmpty のような、値を保持しているだけのような判定であっても、() を付けて呼び出すメソッドとして書くしかなかった、ということになります。

図8 list.Any() を使って実装された IsEmpty メソッド。今までどおり () を付けて呼び出す

extension<T>(IEnumerable<T> list)
{
  public bool IsEmpty()
  {
    if (list == null) return true;
    return !list.Any();
  }
}

まとめ

今後は extension ブロックが使えるので、値を保持しているだけの、変数のように使うものはプロパティとして書くほうが適しています。プロパティに適しているものを extension ブロックの中でプロパティとして定義することで、より自然で違和感のないコードが書けるようになりました。

C#14 新機能