C#14新機能

C#14 新機能 B04_型パラメータありの拡張メソッド

今回は、型パラメータがある場合の拡張メソッドを見ていきます。まずは型パラメータのある拡張メソッドを従来の形で書いてから、これをextensionブロックに変える、という流れは次回に回し、今回は従来の書き方で仕上げるところまでを扱います。

既存の拡張メソッドを確認する

題材にするのは、これまでの回で作成したint型向けの拡張メソッドです。extensionブロックの中にIsZero、IsNegativeという2つのメソッドが定義されています。

図1: 既存のint型向け拡張メソッド(IsZero / IsNegative)

コレクション向けのIsEmptyメソッドを追加する

ここに、コレクションが空かどうかを判定するIsEmptyメソッドを追加します。対象はIEnumerable<T>とし、任意の型のコレクションに対応できるようにします。今回はextensionブロックの中ではなく、クラスの直下に従来どおりのstaticメソッドとして書いていきます。

最初はthisキーワードや型パラメータのTを付けずに書き始め、段階的に次のように変えていきます。

// ① まずは型パラメータなしで書き始める
public static bool IsEmpty(IEnumerable<T> list)
 
// ② thisを付けて拡張メソッドにする
public static bool IsEmpty(this IEnumerable<T> list)
 
// ③ メソッド名の直後に<T>を付けて型パラメータを宣言する
public static bool IsEmpty<T>(this IEnumerable<T> list)

 

このように、使いたい型パラメータTをメソッド名の後ろに<T>という形で宣言することで、任意の型のコレクションに対応した拡張メソッドになります。

nullチェックを含めた実装

IsEmptyという名前のとおり、「値が空かどうか」を判定するメソッドなので、要素が0件のときだけでなく、コレクションそのものがnullのときもtrueを返すようにします。Any()は1件でも要素があればtrueを返すメソッドなので、その否定を使えば「要素がない」かどうかを判定できます。最終的な実装は次のとおりです。

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

 

図2: 完成したIsEmpty<T>メソッド

listがnullのときにreturn trueとしている点がポイントです。拡張メソッドは静的メソッドとして呼び出されるため、たとえ変数がnullであっても例外にならず、その値がそのままlist引数としてメソッド内に渡ってきます。そのため、メソッドの中でこのようにnullチェックを行うことができます。

使用例で動作を確認する

実際に3パターンのList<string>を用意して、IsEmptyの戻り値を確認します。

var listA = new List<string>();
listA.Add("AAA");
var retA = listA.IsEmpty();
 
var listB = new List<string>();
var retB = listB.IsEmpty();
 
List<string> listC = null;
var retC = listC.IsEmpty();

 

図3: listA(要素あり)/listB(0件)/listC(null)を用意する

listAは要素を1件追加しているのでfalse(空ではない)、listBはnewしただけで0件なのでtrue、listCはnullなのでtrueになるはずです。デバッグ実行してウォッチウィンドウで値を確認すると、想定どおりの結果になっていることが分かります。

図4: retA = false、retB = true、retC = true

listCに対してlistC.IsEmpty()と呼び出している部分は、一見するとnull参照で例外になりそうに見えます。しかし実際には拡張メソッドの中を経由して呼び出されるため、IsEmptyメソッドの引数listにnullが渡ってくるだけで、呼び出し自体は例外になりません。そのメソッドの中でif (list == null) return true;というチェックが効いているため、正しくtrueが返っています。

クラス名をIntExからExへ変更する

このクラスはこれまでint型専用の拡張メソッドを格納する目的でIntExという名前にしていましたが、今回コレクションを対象とするメソッドを追加したことで、int専用ではなくなりました。そこでクラス名をExに変更しておきます。

図5: クラス名をIntExからExに変更

nullチェックがなければ例外になることを確認する

最後に、nullチェックが本当に効いているのかを確認するため、試しにif (list == null) return true;の行をコメントアウトしてから実行してみます。

図6: nullチェックをコメントアウトすると、listC.IsEmpty()の実行時にハンドルされていない例外(NullReferenceException)が発生する

この状態で実行すると、listC.Any()の呼び出しでハンドルされていない例外が発生します。コメントを元に戻して再度実行すると、例外にならずに正常に通ることが確認できます。これにより、null時にtrueを返すチェックが正しく機能していることが分かります。

図7: コメントを戻した最終的なIsEmpty<T>メソッド(3個の参照)

まとめ

今回は、型パラメータのある拡張メソッドを従来の書き方で作成しました。ポイントは、メソッド名の直後に<T>を付けて型パラメータを宣言すること、拡張メソッドは静的メソッド呼び出しとして解決されるためnullを渡しても例外にならないこと、そしてその性質を利用してメソッド内でnullチェックを行えることです。次回は、この拡張メソッドをextensionブロックの形に書き換えていきます。

C#14 新機能