C# 14 の拡張メンバー構文で静的クラスを対象にできない原因と代替手段

C# 14 の extension ブロックで Directory などの静的クラスを対象に指定するとコンパイルエラーになる原因を型システムの観点から解説し、静的ヘルパーメソッドと DirectoryInfo 拡張の 2 つの代替手段を示す。

概要

本記事では、C# 14(.NET 10)で導入された extension ブロック構文を用いて静的クラスに拡張メンバーを追加しようとした際に発生するコンパイルエラーを取り上げ、その原因と実用的な代替手段を整理する。 合わせて、C# 3.0 から C# 14 にかけての拡張メンバー機能の変遷についても概説する。


前提・対象環境


問題

C# 14 の extension ブロックを使用して System.IO.Directory のような標準の静的クラスに拡張メンバーを定義しようとすると、コンパイルエラーが発生する。

以下はエラーが発生するコードの例である。

using System.IO;

public static class DirectoryExtensions
{
    // エラー:静的クラス(Directory)を拡張メンバーのターゲットに指定することはできない
    extension(Directory)
    {
        public static void DeleteIfExists(string path)
        {
            if (Directory.Exists(path))
            {
                Directory.Delete(path, true);
            }
        }
    }
}

extension(Directory) の箇所でコンパイラがエラーを報告し、ビルドが通らない。


原因

静的クラスは型として扱えない

C# において static class は「静的メンバーをまとめるためのコンテナ」であり、言語仕様上、静的クラスの名前を型として利用することは一律で禁止されている。 以下の記述はいずれもコンパイルエラーとなる。

記述例 エラーの種別
Directory myDir; 変数の型に静的クラスを使用
List<Directory> list; ジェネリックの型引数に静的クラスを使用
typeof(Directory) typeof 演算子の対象に静的クラスを使用

extension(型名) の括弧内にはコンパイラが有効と認識できる型を指定する必要があるが、Directory はこの規則により型として扱われないため、記述した時点でエラーになる。

コンパイラが生成するメタデータの制約

拡張メンバー構文がコンパイルされる際、コンパイラは対象の型に関するメタデータを生成する。 この処理ではインスタンス化可能な型またはインターフェースの情報が必要であるため、静的クラスを対象とすることはシステム的にも受け入れられない。


解決方法

標準の静的クラスそのものを extension ブロックで拡張することは不可能であるため、以下の 2 つのアプローチが現実的な代替手段となる。


実装例

解決策 A:静的ヘルパークラスとして定義する

拡張メンバー構文にこだわらず、従来の静的クラスにユーティリティメソッドを実装する方法である。 コードの可読性が高く、追加の型知識を必要としないため、最も汎用性の高い選択肢となる。

using System.IO;

public static class DirectoryHelper
{
    /// <summary>
    /// 指定されたパスにディレクトリが存在する場合、安全に削除します。
    /// </summary>
    public static void DeleteIfExists(string path)
    {
        if (Directory.Exists(path))
        {
            Directory.Delete(path, true);
        }
    }
}

呼び出し側では以下のように使用する。

DirectoryHelper.DeleteIfExists(@"C:\Temp\TargetDir");

Directory.DeleteIfExists(...) のようなドット記法での呼び出しはできないが、メソッドの所在が明確であり、可読性の観点からも問題は少ない。

解決策 B:DirectoryInfo に対して拡張メンバーを定義する

DirectoryInfo はインスタンス化可能な通常の型であるため、extension ブロックのターゲットに指定できる。 「拡張メンバーとしてのドット記法での呼び出し」にこだわる場合に有効な手段である。

using System.IO;

public static class DirectoryInfoExtensions
{
    // DirectoryInfo はインスタンス化可能な型なので指定可能
    extension(DirectoryInfo directoryInfo)
    {
        /// <summary>
        /// ディレクトリが存在する場合に安全に削除します。
        /// </summary>
        public void DeleteIfExists()
        {
            if (directoryInfo.Exists)
            {
                directoryInfo.Delete(true);
            }
        }
    }
}

呼び出し側では以下のように使用する。

var dir = new DirectoryInfo(@"C:\Temp\TargetDir");
dir.DeleteIfExists();

DirectoryInfo のインスタンス生成が必要になるが、拡張メンバーとしてスマートに呼び出せる点がメリットとなる。


注意点


代替案・比較

方法 メリット デメリット 適するケース
静的ヘルパークラス(解決策 A) 実装がシンプル、バージョン依存なし Directory.Xxx() 形式で呼び出せない 実用性・移植性を優先する場合
DirectoryInfo への拡張メンバー(解決策 B) ドット記法で呼び出せる、拡張メンバー構文を活用できる インスタンス生成が必要、C# 14 以降限定 拡張メンバーとしての呼び出し形式を統一したい場合

補足:C# 3.0 から C# 14 までの拡張メンバーの変遷

C# における外付けメンバー機能は段階的に拡張されてきた。以下にその主な変遷を示す。

バージョン 対象プラットフォーム 主な変更内容
C# 3.0 .NET Framework 3.5 拡張メソッドの導入public static class 内で第一引数に this キーワードを付与することで、既存の型にインスタンスメソッドを外付けできるようになった。LINQ の実現基盤でもある。
C# 7.2 .NET Core 2.0 / .NET Framework 4.7.2 値型への対応強化ref thisin this 修飾子が利用可能になり、大きな構造体をコピーせず参照渡しで拡張できるようになった。
C# 13 〜 C# 14 .NET 9 〜 .NET 10 拡張メンバー構文(extension ブロック)の導入this 引数による記述から extension(型) ブロックによる宣言へ移行。静的メンバーやプロパティの外付けも可能になった。

C# 14 における「静的メンバーの拡張」は、あくまで「インスタンス化可能な通常の型に対して静的メンバーを追加する機能」である。 Directory などの静的クラスそのものを拡張する機能は現時点でサポートされていない。


まとめ

extension ブロックで静的クラスを対象にできないのは、C# の型システムが静的クラスの名前を型として認識しないためである。 この制約は C# 14 においても変わらない。

対応方針は以下の基準で選択するとよい。

静的クラスに直接アクセスするような記述形式を求める場合、現状では言語仕様上の回避策は存在しない。