C#14新機能

C#14 新機能 B03_extensionブロック

このレクチャーでは、拡張メソッドを整理するための「extensionブロック」について解説します。extensionブロックを使うと、同じ型を拡張する複数のメンバーをグルーピングできるだけでなく、メソッド以外にプロパティなども書けるようになります。

これまでの拡張メソッドの書き方

まずはグルーピングの考え方から見ていきます。次のように、int型を拡張してゼロかどうかを判定するIsZeroという拡張メソッドがあったとします。

図1: 既存のIsZero拡張メソッド(IntEx.IsZero(a) を a.IsZero() に書き換え済みの状態)

public static class IntEx
{
  public static bool IsZero(this int value)
  {
    return value == 0;
  }
}

 

ここにマイナス値かどうかを判定するIsNegativeを追加しようとすると、これまでは同じようにthisキーワードを使ったstaticメソッドとして、もう一つ書き足していくことになります。

図2: 従来の書き方でIsNegativeを追加した状態(IsZeroとは別に、同じ形のstaticメソッドを書き足している)

public static class IntEx
{
  public static bool IsZero(this int value)
  {
    return value == 0;
  }

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

 

このように、拡張したい機能が増えるたびに、同じ形のstaticメソッドをバラバラに書き足していくことになります。

extensionブロックで書き直す

extensionブロックを使うと、この書き方を整理できます。extensionと書いて、括弧の中に拡張したい型と変数名を指定します。これは、これまで拡張メソッドの第一引数に書いていたthis int valueと同じ内容ですが、thisは付けません。

図3: extension(int value) ブロックを追加し、IsZeroの引数(this int value)を削除しようとしている状態

public static class IntEx
{
  extension(int value)
  {

  }

  public static bool IsZero(this int value)
  {
    return value == 0;
  }
}

 

このブロックでIsZeroとIsNegativeを囲むと、まずstaticが不要になります。また、ブロックの引数として宣言したvalueがそのまま使えるので、メソッド自身の引数(this int value)も不要になります。extensionブロックの中身は、この型(今回はint)の拡張になっているので、メソッドを書けばメソッドの拡張に、プロパティを書けばプロパティの拡張になります。

図4: staticとthis int valueを削除し、extensionブロックの引数valueを使う形に書き換えた状態

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

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

 

これまではthis intとバラバラに書いていたものが、extension(int value)というひとつのブロックの中にまとまり、int型を拡張したいメンバーをここに集めて書いておけるようになります。つまりグルーピングができるということです。

図5: IsZeroとIsNegativeの両方がextension(int value)ブロックの中にグルーピングされている状態

呼び出し側のコードは変わらない

extensionブロックを使って書き換えても、呼び出す側のクライアントコードは一切変更する必要がありません。これまでと全く同じ書き方のまま利用できます。

int a = 1;
bool ret1 = a.IsZero();
int b = 0;
bool ret2 = b.IsZero();

 

実際に実行して確認すると、ret1がfalse、ret2がtrueとなり、正しく動作していることが分かります。

図6: デバッグ実行でret1にfalseが代入されていることを確認

もちろんIsNegativeについても、これまでと同様に使うことができます。マイナス値を代入したaに対してIsNegativeを呼び出すと、trueが返ってきます。

int a = -1;
bool ret1 = a.IsNegative();
int b = 0;
bool ret2 = b.IsNegative();

 

図7: マイナス値を代入したaに対してIsNegative()を呼び出している状態

マイナス1を入れたaのIsNegativeはtrueが返り、0を入れたbのIsNegativeはfalseが返ります。

まとめ

extensionと書いて拡張したい型を指定し、そのブロックの中に普通のクラスと同じ感覚でメソッド(やプロパティ)を書くことで、対象の型を拡張するメンバーをグルーピングして整理できます。ブロックの引数として宣言した変数を、ブロック内の各メンバーでそのまま使って処理を書くことができます。

C#14 新機能