次の方法で共有


アセンブリ読み込みのベスト プラクティス

この記事は .NET Framework に固有のものです。 .NET 6 以降のバージョンを含む、.NET の新しい実装には適用されません。

この記事では、 InvalidCastExceptionMissingMethodException、その他のエラーにつながる可能性がある型 ID の問題を回避する方法について説明します。 この記事では、次の推奨事項について説明します。

最初の推奨事項は、 読み込みコンテキストの長所と短所を理解し、他の推奨事項の背景情報を提供します。これらはすべて、読み込みコンテキストの知識に依存するためです。

読み込みコンテキストの長所と短所を理解する

アプリケーション ドメイン内では、アセンブリを次の 3 つのコンテキストのいずれかに読み込んだり、コンテキストなしで読み込んだりできます。

  • 既定の読み込みコンテキストには、グローバル アセンブリ キャッシュ、ランタイムがホストされている場合のホスト アセンブリ ストア (SQL Server など)、およびアプリケーション ドメインの ApplicationBasePrivateBinPath によって検出されたアセンブリが含まれます。 Load メソッドのほとんどのオーバーロードは、アセンブリをこのコンテキストに読み込みます。

  • 読み込み元コンテキストには、ローダーによって検索されない場所から読み込まれたアセンブリが含まれています。 たとえば、アドインは、アプリケーション パスの下にないディレクトリにインストールされる場合があります。 Assembly.LoadFromAppDomain.CreateInstanceFrom、および AppDomain.ExecuteAssembly は、パスによって読み込まれるメソッドの例です。

  • リフレクションのみのコンテキストには、 ReflectionOnlyLoad メソッドと ReflectionOnlyLoadFrom メソッドで読み込まれたアセンブリが含まれています。 このコンテキストのコードは実行できないため、ここでは説明しません。 詳細については、「 方法: アセンブリを Reflection-Only コンテキストに読み込む」を参照してください。

  • リフレクション出力を使用して一時的な動的アセンブリを生成した場合、アセンブリはどのコンテキストにも含まれません。 さらに、 LoadFile メソッドを使用して読み込まれるほとんどのアセンブリはコンテキストなしで読み込まれ、バイト配列から読み込まれるアセンブリは、ID (ポリシーの適用後) がグローバル アセンブリ キャッシュ内にあることを確立しない限り、コンテキストなしで読み込まれます。

実行コンテキストには、次のセクションで説明するように、長所と短所があります。

既定の読み込みコンテキスト

アセンブリが既定の読み込みコンテキストに読み込まれると、それらの依存関係が自動的に読み込まれます。 既定の読み込みコンテキストに読み込まれる依存関係は、既定の読み込みコンテキストまたは読み込み元コンテキストのアセンブリに対して自動的に検出されます。 アセンブリ ID による読み込みでは、不明なバージョンのアセンブリが使用されないようにすることで、アプリケーションの安定性が向上します (「 部分アセンブリ名でのバインドの回避 」セクションを参照してください)。

既定の読み込みコンテキストを使用すると、次のような欠点があります。

  • 他のコンテキストに読み込まれる依存関係は使用できません。

  • プローブ パスの外側の場所から既定の読み込みコンテキストにアセンブリを読み込むことはできません。

読み込み元コンテキスト

読み込みからコンテキストを使用すると、アプリケーション パスの下にないパスからアセンブリを読み込むことができます。したがって、プローブには含まれません。 これにより、パス情報はコンテキストによって維持されるため、依存関係をそのパスから見つけて読み込むことができます。 さらに、このコンテキストのアセンブリでは、既定の読み込みコンテキストに読み込まれる依存関係を使用できます。

Assembly.LoadFrom メソッドを使用してアセンブリを読み込むか、パスで読み込む他のメソッドの 1 つを使用してアセンブリを読み込むには、次の欠点があります。

  • 同じ ID を持つアセンブリが既に読み込みコンテキストに読み込まれている場合、 LoadFrom は、別のパスが指定されている場合でも、読み込まれたアセンブリを返します。

  • アセンブリが LoadFromで読み込まれ、後で既定の読み込みコンテキストのアセンブリが表示名で同じアセンブリを読み込もうとする場合、読み込みの試行は失敗します。 これは、アセンブリが逆シリアル化されるときに発生する可能性があります。

  • アセンブリが LoadFromで読み込まれ、プローブ パスに同じ ID を持つアセンブリが異なる場所に含まれている場合、 InvalidCastExceptionMissingMethodException、またはその他の予期しない動作が発生する可能性があります。

  • LoadFromは、指定したパスFileIOPermissionAccess.ReadFileIOPermissionAccess.PathDiscoveryまたはWebPermissionを要求します。

  • アセンブリのネイティブ イメージが存在する場合は使用されません。

  • アセンブリをドメインニュートラルとして読み込むことができません。

  • .NET Framework バージョン 1.0 および 1.1 では、ポリシーは適用されません。

コンテキストなし

コンテキストなしでの読み込みは、リフレクション出力で作成される一時アセンブリに対する唯一のオプションです。 コンテキストのない読み込みは、同じ ID を持つ複数のアセンブリを 1 つのアプリケーション ドメインに読み込む唯一の方法です。 プローブのコストは回避されます。

バイト配列から読み込まれるアセンブリは、ポリシーの適用時に確立されるアセンブリの ID がグローバル アセンブリ キャッシュ内のアセンブリの ID と一致しない限り、コンテキストなしで読み込まれます。その場合、アセンブリはグローバル アセンブリ キャッシュから読み込まれます。

コンテキストなしでアセンブリを読み込むには、次の欠点があります。

  • 他のアセンブリは、 AppDomain.AssemblyResolve イベントを処理しない限り、コンテキストなしで読み込まれたアセンブリにバインドできません。

  • 依存関係は自動的には読み込まれません。 コンテキストなしで事前に読み込んだり、既定の読み込みコンテキストに事前に読み込んだり、 AppDomain.AssemblyResolve イベントを処理して読み込んだりすることができます。

  • コンテキストなしで同じ ID を持つ複数のアセンブリを読み込むと、同じ ID を持つアセンブリを複数のコンテキストに読み込む場合と同様の型 ID の問題が発生する可能性があります。 「アセンブリを複数のコンテキストに読み込むのを避ける」を参照してください。

  • アセンブリのネイティブ イメージが存在する場合は使用されません。

  • アセンブリをドメインニュートラルとして読み込むことができません。

  • .NET Framework バージョン 1.0 および 1.1 では、ポリシーは適用されません。

部分的なアセンブリ名をバインドしない

部分名前バインドは、アセンブリを読み込むときにアセンブリ表示名 (FullName) の一部のみを指定した場合に発生します。 たとえば、バージョン、カルチャ、公開キー トークンを省略して、アセンブリの単純な名前のみを使用して Assembly.Load メソッドを呼び出すことができます。 または、 Assembly.LoadWithPartialName メソッドを呼び出し、最初に Assembly.Load メソッドを呼び出し、アセンブリの検索に失敗した場合は、グローバル アセンブリ キャッシュを検索し、使用可能な最新バージョンのアセンブリを読み込みます。

部分的な名前のバインドは、次のような多くの問題を引き起こす可能性があります。

  • Assembly.LoadWithPartialName メソッドは、同じ単純な名前を持つ別のアセンブリを読み込む場合があります。 たとえば、2 つのアプリケーションが、グローバル アセンブリ キャッシュに単純な名前 GraphicsLibrary を持つ完全に異なる 2 つのアセンブリをインストールする場合があります。

  • 実際に読み込まれるアセンブリは、下位互換性がない可能性があります。 たとえば、バージョンを指定しないと、プログラムが最初に使用するように書き込まれたバージョンよりもはるかに新しいバージョンが読み込まれる可能性があります。 新しいバージョンを変更すると、アプリケーションでエラーが発生する可能性があります。

  • 実際に読み込まれるアセンブリは、前方互換性がない可能性があります。 たとえば、アセンブリの最新バージョンを使用してアプリケーションをビルドしてテストしたが、部分バインディングでは、アプリケーションで使用する機能がない以前のバージョンが読み込まれる可能性があります。

  • 新しいアプリケーションをインストールすると、既存のアプリケーションが壊れる可能性があります。 LoadWithPartialName メソッドを使用するアプリケーションは、互換性のない新しいバージョンの共有アセンブリをインストールすることで破損する可能性があります。

  • 予期しない依存関係の読み込みが発生する可能性があります。 依存関係を共有する 2 つのアセンブリを読み込み、それらを部分バインドで読み込むと、ビルドまたはテストされていないコンポーネントを使用して 1 つのアセンブリが作成される可能性があります。

原因となる可能性のある問題のため、 LoadWithPartialName メソッドは古い形式としてマークされています。 代わりに Assembly.Load メソッドを使用し、完全なアセンブリ表示名を指定することをお勧めします。 読み込みコンテキストの長所と短所を理解し、既定の読み込みコンテキストに切り替えることを検討してください

アセンブリの読み込みが容易になるため、 LoadWithPartialName メソッドを使用する場合は、不明なバージョンのアセンブリを自動的に使用するよりも、不足しているアセンブリを識別するエラー メッセージでアプリケーションが失敗する可能性が高いことを検討してください。これにより、予期しない動作やセキュリティ ホールが発生する可能性があります。

アセンブリを複数のコンテキストに読み込むのを避ける

アセンブリを複数のコンテキストに読み込むと、型 ID の問題が発生する可能性があります。 同じ型が同じアセンブリから 2 つの異なるコンテキストに読み込まれる場合は、同じ名前の 2 つの異なる型が読み込まれたかのように見えます。 一方の型を別の型にキャストしようとすると、InvalidCastException がスローされ、「型 MyType は型 MyType にキャストできない」と混乱を招くメッセージが表示されます。

たとえば、 ICommunicate インターフェイスが、 Utility という名前のアセンブリで宣言され、プログラムおよびプログラムが読み込む他のアセンブリによって参照されるとします。 これらの他のアセンブリには、 ICommunicate インターフェイスを実装する型が含まれています。これにより、プログラムで使用できるようになります。

次に、プログラムの実行時に何が起こるかを検討します。 プログラムによって参照されるアセンブリは、既定の読み込みコンテキストに読み込まれます。 Load メソッドを使用して、その ID によってターゲット アセンブリを読み込む場合は、既定の読み込みコンテキストになり、依存関係も同じになります。 プログラムとターゲット アセンブリの両方で、同じ Utility アセンブリが使用されます。

ただし、 LoadFile メソッドを使用して、そのファイル パスでターゲット アセンブリを読み込むとします。 アセンブリはコンテキストなしで読み込まれるため、依存関係は自動的に読み込まれません。 依存関係を提供するAppDomain.AssemblyResolve イベントのハンドラーがあり、Utility メソッドを使用してコンテキストなしでLoadFile アセンブリを読み込む場合があります。 ターゲット アセンブリに含まれる型のインスタンスを作成し、それをICommunicate型の変数に割り当てようとすると、ランタイムが InvalidCastException アセンブリの 2 つのコピー内のICommunicate インターフェイスを異なる型と見なしているため、Utilityがスローされます。

アセンブリを複数のコンテキストに読み込むことができるシナリオは、他にも多数あります。 最適な方法は、アプリケーション パス内のターゲット アセンブリを再配置し、完全な表示名を持つ Load メソッドを使用して競合を回避することです。 その後、アセンブリが既定の読み込みコンテキストに読み込まれ、両方のアセンブリで同じ Utility アセンブリが使用されます。

ターゲット アセンブリをアプリケーション パスの外側に残す必要がある場合は、 LoadFrom メソッドを使用して、読み込みからコンテキストに読み込むことができます。 ターゲット アセンブリがアプリケーションの Utility アセンブリへの参照を使用してコンパイルされた場合、アプリケーションが既定の読み込みコンテキストに読み込んだ Utility アセンブリが使用されます。 ターゲット アセンブリがアプリケーション パスの外部にある Utility アセンブリのコピーに依存している場合は、問題が発生する可能性があることに注意してください。 アプリケーションが Utility アセンブリを読み込む前に、そのアセンブリが読み込みからコンテキストに読み込まれると、アプリケーションの読み込みは失敗します。

既定の読み込みコンテキストへの切り替えを検討 する」セクションでは、 LoadFileLoadFromなどのファイル パスの読み込みを使用する代替方法について説明します。

アセンブリの複数のバージョンを同じコンテキストに読み込むのを避ける

アセンブリの複数のバージョンを 1 つの読み込みコンテキストに読み込むと、型 ID の問題が発生する可能性があります。 同じアセンブリの 2 つのバージョンから同じ型が読み込まれる場合は、同じ名前の 2 つの異なる型が読み込まれたかのように見えます。 一方の型を別の型にキャストしようとすると、InvalidCastException がスローされ、「型 MyType は型 MyType にキャストできない」と混乱を招くメッセージが表示されます。

たとえば、プログラムで 1 つのバージョンの Utility アセンブリを直接読み込み、後で別のバージョンの Utility アセンブリを読み込む別のアセンブリを読み込む場合があります。 または、コーディング エラーが発生すると、アプリケーション内の 2 つの異なるコード パスが異なるバージョンのアセンブリを読み込む可能性があります。

既定の読み込みコンテキストでは、 Assembly.Load メソッドを使用し、異なるバージョン番号を含む完全なアセンブリ表示名を指定すると、この問題が発生する可能性があります。 コンテキストなしで読み込まれるアセンブリの場合、 Assembly.LoadFile メソッドを使用して同じアセンブリを異なるパスから読み込むことで問題が発生する可能性があります。 ランタイムは、ID が同じである場合でも、異なるパスから読み込まれる 2 つのアセンブリを異なるアセンブリと見なします。

型 ID の問題に加えて、アセンブリの 1 つのバージョンから読み込まれた型が別のバージョンの型を想定するコードに渡された場合、アセンブリの複数のバージョンによって MissingMethodException が発生する可能性があります。 たとえば、コードでは、新しいバージョンに追加されたメソッドが必要な場合があります。

バージョン間で型の動作が変更された場合は、より微妙なエラーが発生する可能性があります。 たとえば、メソッドが予期しない例外をスローしたり、予期しない値を返したりする場合があります。

コードを慎重に確認して、アセンブリの 1 つのバージョンのみが読み込まれるようにします。 AppDomain.GetAssemblies メソッドを使用して、任意の時点で読み込まれるアセンブリを決定できます。

既定の読み込みコンテキストへの切り替えを検討する

アプリケーションのアセンブリの読み込みと配置のパターンを確認します。 バイト配列から読み込まれるアセンブリを削除できますか? アセンブリをプローブ パスに移動できますか? アセンブリがグローバル アセンブリ キャッシュまたはアプリケーション ドメインのプローブ パス (つまり、 ApplicationBasePrivateBinPath) にある場合は、その ID でアセンブリを読み込むことができます。

すべてのアセンブリをプローブ パスに配置できない場合は、.NET Framework アドイン モデルの使用、アセンブリのグローバル アセンブリ キャッシュへの配置、アプリケーション ドメインの作成などの代替手段を検討してください。

.NET Framework Add-In モデルの使用を検討する

読み込み元コンテキストを使用してアドインを実装する場合 (通常はアプリケーション ベースにはインストールされません)、.NET Framework アドイン モデルを使用します。 このモデルは、アプリケーション ドメインまたはプロセス レベルで分離を提供します。アプリケーション ドメインを自分で管理する必要はありません。 アドイン モデルの詳細については、「アドイン と機能拡張」を参照してください。

グローバル アセンブリ キャッシュの使用を検討する

既定の読み込みコンテキストの利点を失ったり、他のコンテキストの欠点を引き受けたりすることなく、アプリケーション ベースの外部にある共有アセンブリ パスの利点を得るために、アセンブリをグローバル アセンブリ キャッシュに配置します。

アプリケーション ドメインの使用を検討する

一部のアセンブリをアプリケーションのプローブ パスに配置できない場合は、それらのアセンブリの新しいアプリケーション ドメインを作成することを検討してください。 AppDomainSetupを使用して新しいアプリケーション ドメインを作成し、AppDomainSetup.ApplicationBase プロパティを使用して、読み込むアセンブリを含むパスを指定します。 プローブするディレクトリが複数ある場合は、 ApplicationBase をルート ディレクトリに設定し、 AppDomainSetup.PrivateBinPath プロパティを使用してプローブするサブディレクトリを識別できます。 または、複数のアプリケーション ドメインを作成し、各アプリケーション ドメインの ApplicationBase をアセンブリの適切なパスに設定することもできます。

Assembly.LoadFrom メソッドを使用してこれらのアセンブリを読み込むことができることに注意してください。 これらはプローブ パス内にあるため、ロード元コンテキストではなく、既定のロードコンテキストに読み込まれます。 ただし、 Assembly.Load メソッドに切り替え、アセンブリの完全な表示名を指定して、正しいバージョンが常に使用されるようにすることをお勧めします。

こちらも参照ください