ダイジェスト
- C#では、既存クラスに独自メソッドを追加する方法として、partialクラス(部分クラス)を使用する方法と、拡張メソッドを使用する方法があります。ここでは両者の特徴・用途や違いについて説明します。
- partialクラスに類似するものとして「partialメソッド(部分メソッド)」がありますが、用途が異なるのでここでは割愛します。
観点 | partialクラスのメソッド | 拡張メソッド |
---|---|---|
特徴・用途 | 拡張元クラス内部へのアクセスが可。 ユーザと自動生成のコードを分離。 大規模なクラス開発や分業に適している。 | 拡張元クラス内部へのアクセス不可。 他者クラス・パッケージの安全な拡張が可。 プロジェクトの独自拡張に適している。 |
拡張可能な範囲※1 | 同一プロジェクト・アセンブリ内で拡張可 | 異なるアセンブリでも拡張可 |
拡張元種別の制約 | クラス、インターフェイス、 構造体、レコード | クラス、抽象クラス、インターフェイス、 組み込み型、構造体、レコード |
拡張元の宣言制約 | 拡張元でpartial宣言が必要 | 制約なし(sealedでも拡張可) |
拡張先の宣言制約 | 拡張先でもpartial宣言が必要 | staticメソッド宣言が必要 |
拡張元へのアクセス | private/protectedメンバにアクセス可 | publicメンバのみアクセス可 |
- ※1:「アセンブリ」とは、.exe, .dll等の.NETアプリケーションの構成要素。
詳細
partialクラスのメソッド
- partialクラスを使用することで、自動生成コードとユーザコードを異なるファイルに分離しつつ、同じクラスとして処理を実装できます。自動生成時のユーザコードの上書きを防ぎ、ビジネスロジック等のユーザ独自の処理の追加が可能になります。また、ユーザコードのファイルには、複雑かつ大量の自動生成コードは含まれないので、ユーザコードの可読性が高まります。
- Visual StudioでWebやGUIなどのUIを開発する場合、デザインツールにより、UIを実現するためのpartialクラス(ファイル)が自動生成されます。ユーザはpartialクラス(ファイル)を定義し、UIの制御やビジネスロジックを実装します。UI変更に伴うコードの自動生成が行われても、ファイルが分離されているので、ユーザコードは上書きされません。同一クラスの扱いなので、ユーザコードから自動生成されたUI部品に対して直接(無駄なく)アクセスができます。
- .NETのDB操作ライブラリ(ORマッパー)であるEntity Framework Coreでは、リバースエンジニアリングの機能(Database-Firstのスキャフォルディング)を使用して、既存データベースの情報に基づいて、DB操作を行うpartialクラス(DBContext, Entityファイル)を自動生成できます。同様に、ユーザはpartialクラス(ファイル)を定義することで、データベース変更に伴うコードの自動生成でもユーザコードは上書きされず、独自の処理を追加することができます。
- .NETの当初のDB操作フレームワークであるデータセット(TableAdapter)でも同じコンセプトが採用されていました。
- partialクラス(とpartialメソッド)はC# 2.0(Visual Studio 2005)から導入されました。それまでのWebやGUI開発では、自動生成のコードとユーザのコードをトリッキーな方法で統合していたそうです。
- 大規模なクラス開発では、用途や機能等でファイルを分割して管理できるので、複数の担当者で平行して開発することもできます。
- 私見ですが、partialクラスやメソッドの特徴やその歴史を踏まえると、Visual Studioツール群によるコード自動生成を意識して導入された機能に思えます。また、「大規模なクラス開発」の用途では、設計を見直してクラス分割した方が品質が良くなると思うので、業務アプリ開発でこの用途での使い方はしないと思います。
拡張メソッド
- 拡張メソッドは、拡張元クラス内部にはアクセスできませんが、結果として拡張元クラスの動作に影響なく簡単にメソッドを追加することができます。また、任意の型にメソッドを追加できるので、業務アプリ開発での型・クラスの機能拡張に適しています。
- 拡張元のクラスのprivate/protectedメンバにアクセスできませんが、逆にこれは、拡張元クラスの動作に影響を与えません。
- 拡張元のソースコードやコンパイルも不要なので、簡単にメソッドを拡張できます。
- 拡張元の型を問わないため、int, byte, string等の組み込み型やクラス・抽象クラス・インターフェイス、nugetで取得した各種パッケージのクラスに、独自のメソッドを追加できます。
- 拡張メソッドでは、インターフェイスに対しても拡張メソッドを実装できるので、疑似的に抽象クラスのように扱えます。
- C#では、単一クラスからの継承(単一継承)のみサポートされており、複数クラスからの継承(多重継承)はサポートされていません。しかしながら、この疑似的な抽象クラスを使用することで、疑似的に多重継承を実現できます。1234567891011121314151617181920212223242526public interface ITest1 { }public interface ITest2 { }public static class ITestExtension{// ITest1, ITest2の拡張メソッドの定義public static int Method1(this ITest1 test1, int val) => val * 1;public static int Method2(this ITest2 test2, int val) => val * 2;}public class Test3 : ITest1, ITest2{public int Method3(int val) => val * 3;}public class Test{[Fact]public void MultipleInterfaceTest(){// 多重継承したように複数の基底クラスのメソッドを使用可var test3 = new Test3();Assert.Equal(1, test3.Method1(1)); // ITest1のメソッドAssert.Equal(4, test3.Method2(2)); // ITest2のメソッドAssert.Equal(9, test3.Method3(3));}}
- 通常のクラスやpartialクラスのインスタンスでメソッドを実行する際、インスタンスがnullの場合はNullReferenceException例外が発生します。拡張メソッドの場合、NullReferenceExceptionは発生せず、拡張メソッドの実行が可能です。
- Moqを使う場合、拡張メソッドとして実装したメソッドはMock化できません。対象メソッド内の実装コードを把握し、それに合わせたMockの作成が必要になるので、少々難易度が上がります。
参考
partialメソッドの概要
- partialメソッドは、C# 3.0(Visual Studio 2008)でリリースされました。(partialクラスは C# 2.0でリリースです。)
- ツールによるコード自動生成時、ユーザが実装するかどうかを決められるメソッドを定義するために、partialメソッドが使用されます。
- ユーザは、ツールで生成されたpartialメソッドの内容を、必要に応じて実装(overrideするイメージ)することができます。ユーザがpartialメソッドを実装しない場合、そのメソッドは宣言されていなかったのと同じように、コンパイル時に完全に削除されます。
- C# 9.0ではpartialメソッドが強化され、戻り値や引数の制約がなくなりました。また、これまでとは逆に、自動生成するpartialメソッドの仕様を、ユーザがツールに対して指示できるようになりました。
参考資料
- マイクロソフト公式
- その他