例外処理

【C#】InvalidProgramExceptionエラーの原因と対処法まとめ

InvalidProgramExceptionはCLRが壊れたILや不正なメタデータを検出した際に発生する例外です。

ビルド設定のx86/x64不一致、破損DLL、動的コード生成バグなどが主因となるため、プラットフォーム統一、参照DLLの置き直し、クリーン再ビルド、最新コンパイラ適用で多くは解決します。

InvalidProgramExceptionとは

例外の定義と特徴

InvalidProgramException は、C# のプログラム実行中に発生する例外の一つで、主に実行時にプログラムの中身が無効であると共通言語ランタイム(CLR)が判断した場合にスローされます。

具体的には、コンパイルされた中間言語(IL)が正しくない、または破損している場合に発生します。

この例外は、通常のコードのバグとは異なり、ILコードの構造的な問題や、JITコンパイラが処理できない不正な命令が含まれている場合に起こります。

たとえば、メソッドのILが不正な形式であったり、メタデータが壊れている場合などが該当します。

特徴としては、以下の点が挙げられます。

  • 実行時に発生し、通常の例外処理で捕捉されることが多い
  • プログラムのビルドやコンパイル段階での問題が原因であることが多い
  • 例外メッセージには「プログラムが無効です」や「ILが不正です」といった内容が含まれることが多い
  • 通常のコードロジックエラーとは異なり、ILコードやアセンブリの整合性に起因する

この例外が発生すると、プログラムの実行が停止し、原因を特定しない限り正常に動作しません。

したがって、発生した場合はビルド設定や依存関係、ILコードの生成過程を重点的に調査する必要があります。

BadImageFormatExceptionとの違い

InvalidProgramException と似た例外に BadImageFormatException があります。

両者はどちらもプログラムの実行時に発生し、アセンブリやILコードの問題を示しますが、原因や発生状況に違いがあります。

例外名発生原因の概要発生タイミング主な特徴・違い
InvalidProgramExceptionILコードが不正、またはJITコンパイラが処理できない無効なプログラムが存在する場合実行時(JITコンパイル時)ILの構造的な問題やメソッドの不正なIL命令が原因。プログラムの中身が無効であることを示します。
BadImageFormatExceptionアセンブリのフォーマットが不正、またはプラットフォームの不一致(例:x86とx64)実行時(アセンブリ読み込み時)アセンブリファイル自体が破損しているか、ビット数の不一致などで読み込めない場合に発生。

BadImageFormatException は、主にアセンブリのファイル形式やプラットフォームの不整合に起因し、CLRがアセンブリを読み込めない場合に発生します。

一方で、InvalidProgramException はアセンブリは読み込めているものの、その中のILコードが不正である場合に発生します。

たとえば、32ビット用にビルドされたDLLを64ビットプロセスで読み込もうとすると BadImageFormatException が発生しますが、ILコードの生成ミスやコンパイラのバグで不正なILが生成された場合は InvalidProgramException が発生します。

このように、両者は似ていますが、発生原因と対処方法が異なるため、例外の種類を正しく把握することが重要です。

発生タイミング

InvalidProgramException は、主に以下のタイミングで発生します。

  • JITコンパイル時

C#のコードは中間言語(IL)にコンパイルされ、実行時にJIT(Just-In-Time)コンパイラによってネイティブコードに変換されます。

このJITコンパイルの段階で、ILコードに不正な命令や構造的な問題があると、InvalidProgramException がスローされます。

  • 動的コード生成時

System.Reflection.Emit や Expression Tree を使って動的にILコードを生成する場合、生成したILが不正だとこの例外が発生します。

たとえば、ローカル変数の型が不整合だったり、例外処理ブロックの構造が間違っている場合です。

  • アセンブリの読み込み後、メソッド呼び出し時

アセンブリ自体は読み込めても、特定のメソッドを呼び出した際にそのメソッドのILが不正であると判明し、例外が発生します。

  • AOT(Ahead-Of-Time)コンパイル環境での問題

UnityのIL2CPPやXamarinのAOTコンパイルなど、ILを事前にネイティブコードに変換する環境で、ILの不整合が原因で実行時に例外が発生することがあります。

このように、InvalidProgramException は実行時のコード変換やメソッド呼び出しの直前に発生することが多いです。

発生した場合は、どのメソッドで例外が起きているかを特定し、そのメソッドのILコードやビルド設定を重点的に調査することが重要です。

以下に、InvalidProgramException が発生する典型的な例を示します。

using System;
class Program
{
    static void Main()
    {
        try
        {
            // 故意に不正なILを生成することは難しいため、
            // ここでは例外が発生する可能性のあるコードを示します。
            InvalidMethod();
        }
        catch (InvalidProgramException ex)
        {
            Console.WriteLine("InvalidProgramExceptionが発生しました:");
            Console.WriteLine(ex.Message);
        }
    }
    static void InvalidMethod()
    {
        // ここに不正なILが含まれていると仮定します。
        // 通常のC#コードでは発生しにくいですが、
        // 動的生成やコンパイラバグで発生することがあります。
        Console.WriteLine("このメソッドでInvalidProgramExceptionが発生する可能性があります。");
    }
}
InvalidProgramExceptionが発生しました:
プログラムが無効です。

この例はあくまでイメージですが、実際には動的IL生成やコンパイラのバグ、ビルド設定の不整合などが原因で発生します。

発生原因を分類する

コンパイル時起因のIL破損

旧バージョンのC#コンパイラバグ

古いC#コンパイラには、ILコードを正しく生成できないバグが存在することがあります。

特にC# 5以前のバージョンでは、特定の構文やジェネリックの組み合わせで不正なILが生成されるケースが報告されています。

こうしたバグにより、ビルドは成功しても実行時にInvalidProgramExceptionが発生することがあります。

たとえば、非同期メソッドの状態マシン生成や、複雑なラムダ式の展開時にILが破損しやすい傾向があります。

これらはコンパイラの内部処理の問題であり、ソースコード上は特に問題がないように見えることも多いです。

対策としては、最新のC#コンパイラや.NET SDKにアップデートすることが有効です。

Visual Studioのバージョンアップや、<LangVersion>タグをlatestに設定することで、バグ修正済みのコンパイラを利用できます。

サードパーティ製コードジェネレータの不具合

コードジェネレータやテンプレートエンジン、ORMツールなどのサードパーティ製品が生成するILコードに問題がある場合もあります。

これらのツールは自動的にコードを生成するため、内部のロジックにバグがあると不正なILが生成され、InvalidProgramExceptionを引き起こします。

特に、動的にコードを生成してアセンブリに埋め込むタイプのツールは注意が必要です。

生成されたコードの型やメソッドのシグネチャが不整合だったり、例外処理の構造が不正な場合に例外が発生します。

対処法としては、ツールのバージョンアップや、生成コードの手動検査、可能であれば生成コードのILを逆アセンブルして問題箇所を特定することが挙げられます。

実行時のプラットフォーム不一致

x86とx64のミスマッチ

32ビット(x86)と64ビット(x64)のアーキテクチャが混在すると、InvalidProgramExceptionBadImageFormatExceptionが発生しやすくなります。

たとえば、64ビットプロセスで32ビット用にビルドされたDLLを読み込もうとすると問題が起きます。

このミスマッチは、プロジェクトのビルド設定でPlatformTargetが適切に設定されていない場合に起こります。

Visual Studioの「ビルド」タブでAny CPUx64x86の設定を確認し、実行環境に合わせて統一することが重要です。

また、Prefer 32-bitオプションが有効になっていると、64ビット環境でも32ビットモードで動作しようとするため、意図しない動作を招くことがあります。

AnyCPUでのP/Invoke呼び出し

AnyCPU設定のアセンブリが、ネイティブコードのDLLをP/Invokeで呼び出す場合、呼び出し先のDLLのビット数と実行プロセスのビット数が一致しないとInvalidProgramExceptionが発生することがあります。

たとえば、64ビット環境でAnyCPUビルドのマネージコードが32ビットのネイティブDLLを呼び出すと、ILコード自体は正しくても実行時に例外が発生します。

この問題を回避するには、P/Invokeで呼び出すDLLのビット数に合わせて、マネージコードのビルド設定を明示的にx86またはx64に固定することが推奨されます。

動的コード生成の問題

Reflection.EmitでのILミス

System.Reflection.Emitを使って動的にILコードを生成する際、IL命令の順序や型の整合性が正しくないとInvalidProgramExceptionが発生します。

たとえば、ローカル変数の型を間違えたり、例外処理ブロックの開始・終了が不正確だとJITコンパイラがエラーを検出します。

以下は、Reflection.Emitでローカル変数の型を誤って定義した場合の例です。

using System;
using System.Reflection;
using System.Reflection.Emit;
class Program
{
    static void Main()
    {
        try
        {
            DynamicMethod method = new DynamicMethod("InvalidMethod", typeof(void), Type.EmptyTypes);
            ILGenerator il = method.GetILGenerator();
            // ローカル変数をint型で宣言
            LocalBuilder local = il.DeclareLocal(typeof(int));
            // しかし、string型の値を代入しようとしている(型不整合)
            il.Emit(OpCodes.Ldstr, "不正な型代入");
            il.Emit(OpCodes.Stloc, local);
            il.Emit(OpCodes.Ret);
            var action = (Action)method.CreateDelegate(typeof(Action));
            action();
        }
        catch (InvalidProgramException ex)
        {
            Console.WriteLine("InvalidProgramExceptionが発生しました:");
            Console.WriteLine(ex.Message);
        }
    }
}
InvalidProgramExceptionが発生しました:
プログラムが無効です。

この例では、ローカル変数の型と代入しようとする値の型が一致していないため、JITコンパイラがILの不整合を検出し例外をスローしています。

Expression TreeのJIT最適化バグ

Expression Treeを使って動的にコードを生成し、コンパイルした際にJITコンパイラの最適化処理が原因でInvalidProgramExceptionが発生することがあります。

特に複雑な式や再帰的な式の展開で発生しやすいです。

この問題は.NETの特定バージョンで報告されており、最新のランタイムにアップデートすることで解消されることが多いです。

また、Expression Treeの構造を単純化することも有効です。

DLL・NuGetパッケージの破損

ダウンロード途中での壊れ

NuGetパッケージやDLLファイルがダウンロード途中で破損した場合、アセンブリのメタデータやILコードが不完全になり、InvalidProgramExceptionが発生することがあります。

特にネットワーク障害やキャッシュの不整合が原因です。

この場合は、パッケージの再インストールやキャッシュのクリアを行うことで解決します。

Visual Studioの「パッケージマネージャーコンソール」でdotnet nuget locals --clear allコマンドを実行し、ローカルキャッシュをクリアすることが推奨されます。

不正な署名やバージョン不整合

署名付きのアセンブリで署名が破損していたり、依存関係のバージョンが不整合な場合もInvalidProgramExceptionが発生します。

たとえば、異なるバージョンの同一DLLが混在していると、ILコードの整合性が崩れることがあります。

依存関係のバージョンを統一し、署名の検証を行うことが重要です。

Assembly Binding Log Viewer (Fuslogvw.exe)を使って依存関係の読み込み状況を確認するとよいでしょう。

AOT・Unity・Xamarin特有の要因

IL2CPP変換時の欠落

UnityのIL2CPPはILコードをC++に変換してネイティブコードにコンパイルしますが、この変換過程で特定のメソッドや型が正しく変換されないことがあります。

結果として、実行時にInvalidProgramExceptionが発生することがあります。

特にリフレクションで参照されるメソッドが変換対象から除外されている場合や、ジェネリック型の特殊化が不完全な場合に起こりやすいです。

対策としては、link.xmlファイルで必要な型やメソッドを明示的に保持する設定を行うことが推奨されます。

リンク時ストリッピング

XamarinやUnityのビルドプロセスで不要なコードを削除する「ストリッピング」機能が有効になっていると、実際には使用しているメソッドや型が誤って削除され、ILコードの整合性が崩れることがあります。

これにより、実行時にInvalidProgramExceptionが発生します。

ストリッピングの設定を見直し、必要なコードを除外リストに追加することで回避可能です。

オブファスケーションの副作用

コントロールフロー変換の失敗

オブファスケーションツールはコードの解析を困難にするために、コントロールフローを複雑化する変換を行います。

しかし、この変換が不完全だったり、ILコードの構造を破壊するとInvalidProgramExceptionが発生します。

特に例外処理ブロックやジャンプ命令の不整合が原因となることが多いです。

オブファスケーションの設定でコントロールフロー変換を無効化するか、例外処理を含むメソッドを除外することが対策になります。

無効なメタデータ生成

オブファスケーション時にメタデータの書き換えが誤って行われると、型情報やメソッドシグネチャが不正になり、ILコードの整合性が崩れます。

これによりInvalidProgramExceptionが発生します。

メタデータの保護設定を適切に行い、信頼できるオブファスケーションツールを使用することが重要です。

また、オブファスケーション後にIL検証ツールを使って問題がないかチェックすることも推奨されます。

代表的なスタックトレース例

コンパイラ生成メソッドでの発生

C#のコンパイラは、非同期メソッドやイテレータ、ラムダ式などの特殊な構文を使うと、内部的に状態マシンや匿名メソッドを生成します。

これらのコンパイラ生成メソッドでILコードに問題があると、InvalidProgramExceptionが発生しやすくなります。

以下は、非同期メソッドの状態マシン生成に起因する例外のスタックトレース例です。

System.InvalidProgramException: プログラムが無効です。
   場所 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[[System.Int32, System.Private.CoreLib]].Start[[MyNamespace.MyClass+<MyAsyncMethod>d__0, MyAssembly]](!!0&)
   場所 MyNamespace.MyClass.<MyAsyncMethod>d__0.MoveNext()
   場所 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1`1.SetResult(!0)
   場所 System.Threading.Tasks.Task`1.TrySetResult(!0)
   場所 System.Threading.Tasks.TaskCompletionSource`1.SetResult(!0)

この例では、<MyAsyncMethod>d__0という名前のコンパイラ生成クラスのMoveNextメソッドで例外が発生しています。

これは非同期メソッドの状態遷移を管理するメソッドであり、ILコードの破損やコンパイラのバグが原因でInvalidProgramExceptionがスローされることがあります。

このような場合は、C#コンパイラのバージョンアップや、非同期メソッドの書き方を見直すことが有効です。

また、プロジェクトのビルド設定やターゲットフレームワークの整合性も確認してください。

自作ライブラリ内部での発生

自作ライブラリの内部でInvalidProgramExceptionが発生するケースも多く見られます。

特に、動的にコードを生成したり、複雑なジェネリック型やunsafeコードを使っている場合に起こりやすいです。

以下は、自作ライブラリのメソッド呼び出し時に発生した例のスタックトレースです。

System.InvalidProgramException: プログラムが無効です。
   場所 MyLibrary.InternalClass.ProcessData(System.String data)
   場所 MyLibrary.PublicClass.Execute()
   場所 MyApp.Program.Main(System.String[] args)

この例では、MyLibrary.InternalClass.ProcessDataメソッド内で例外が発生しています。

ILコードの不整合や、ビルド時の設定ミス、依存関係の不整合が原因となることが多いです。

対策としては、問題のメソッドを特定し、IL検証ツール(ILDASMやILSpy)で逆アセンブルしてILコードの整合性を確認します。

また、ビルド設定の見直しや、依存DLLのバージョン管理も重要です。

サードパーティDLL呼び出し時の発生

サードパーティ製のDLLを呼び出す際にInvalidProgramExceptionが発生することもあります。

特に、DLLのバージョン違いやプラットフォーム不一致、破損したDLLを読み込んだ場合に起こりやすいです。

以下は、サードパーティDLLのメソッド呼び出しで発生した例のスタックトレースです。

System.InvalidProgramException: プログラムが無効です。
   場所 ThirdPartyLibrary.SomeClass.SomeMethod()
   場所 MyApp.Program.Main(System.String[] args)

この例では、ThirdPartyLibrary.SomeClass.SomeMethodの呼び出し時に例外が発生しています。

DLLのビルド設定や依存関係の不整合、署名の問題などが原因となることが多いです。

対処法としては、DLLの再取得やバージョンアップ、プラットフォーム設定の統一を行います。

また、DLLの整合性を検証するためにIL検証ツールを使うことも有効です。

場合によっては、サードパーティのサポートに問い合わせることも検討してください。

調査とデバッグのアプローチ

再現手順の最小化

問題コードの切り出し

InvalidProgramExceptionの調査では、まず問題が発生するコードを最小限に切り出すことが重要です。

大規模なプロジェクトや複雑な処理の中で例外が発生すると、原因特定が難しくなります。

そこで、問題の発生するメソッドやクラスを単独のコンソールアプリやテストプロジェクトに切り出し、再現性のある最小限のコードに絞り込みます。

例えば、動的コード生成を行う部分や、非同期メソッド、ジェネリックを多用する箇所を中心に切り出し、例外が発生するかどうかを確認します。

これにより、問題の範囲を限定し、原因の特定や修正がしやすくなります。

データ・構成の固定

例外が特定の入力データや環境設定に依存している場合は、再現性を高めるためにデータや構成を固定します。

たとえば、外部ファイルやデータベースの状態、環境変数、設定ファイルの内容を一定に保ち、同じ条件下で何度もテストを行います。

これにより、偶発的な環境依存の問題を排除し、原因の切り分けが容易になります。

特に複数の依存関係が絡む場合は、依存DLLのバージョンや配置も固定することが望ましいです。

ILを解析するツール

ILDASMでのメタデータ確認

ILDASM(IL Disassembler)は、Microsoftが提供するILコードの逆アセンブルツールです。

アセンブリのメタデータやIL命令を視覚的に確認でき、ILの構造やメソッドの中身を詳細に調査できます。

ILDASMを使うと、メソッドのILコードが正しく生成されているか、例外処理ブロックの構造が整っているかなどを確認できます。

特にInvalidProgramExceptionの原因がILの破損や不整合にある場合、問題箇所の特定に役立ちます。

ILSpy / dotPeekでの逆アセンブル

ILSpydotPeekは、.NETアセンブリを逆アセンブルしてC#のソースコードに近い形で表示できるツールです。

これらを使うと、ILコードだけでなく、元のソースコードの構造やロジックを把握しやすくなります。

問題のあるメソッドやクラスを特定し、どのようなコードが生成されているかを確認することで、コンパイル時の問題やコード生成の誤りを見つけやすくなります。

ILVerifyでの静的検査

ILVerifyは、ILコードの静的検証ツールで、ILの整合性やメタデータの正当性をチェックします。

Visual Studioの拡張機能やコマンドラインツールとして利用可能です。

ILVerifyを使うと、ILの不正な命令や構造的な問題を事前に検出でき、InvalidProgramExceptionの原因を早期に発見できます。

CI環境に組み込むことで、ビルド時に問題を検出することも可能です。

JITデバッグオプションの活用

COMPlus_ReadyToRun=0の設定

.NETのJITコンパイルには、事前コンパイル済みのコード(ReadyToRun)が利用されることがありますが、これが原因で問題が隠れる場合があります。

環境変数COMPlus_ReadyToRun=0を設定すると、ReadyToRunコードを無効化し、純粋なJITコンパイルを強制できます。

これにより、JITコンパイル時の問題が明確になり、InvalidProgramExceptionの再現性が向上します。

コマンドプロンプトやPowerShellで以下のように設定してからアプリを実行します。

set COMPlus_ReadyToRun=0
dotnet run

COMPlus_JitStressでの再現性向上

COMPlus_JitStressはJITコンパイラのストレステスト用環境変数で、JITの最適化やコード生成の挙動を変化させます。

これを設定すると、通常は発生しにくいILの問題が顕在化しやすくなります。

たとえば、COMPlus_JitStress=1COMPlus_JitStress=2などの値を設定して実行することで、InvalidProgramExceptionの再現性を高め、原因調査に役立てられます。

プラットフォーム設定の確認

Visual Studioのビルド構成

Visual Studioのビルド設定で、Platform TargetPrefer 32-bitの設定が不適切だと、実行時にInvalidProgramExceptionが発生することがあります。

特に、x86とx64の混在やAnyCPU設定の誤用が原因です。

プロジェクトのプロパティから「ビルド」タブを開き、Platform targetを実行環境に合わせて明示的に設定します。

また、Prefer 32-bitのチェックを外すか入れるかも環境に応じて調整してください。

PublishSingleFile時の注意点

.NET Coreや.NET 5以降のPublishSingleFile機能を使うと、すべての依存ファイルを単一の実行ファイルにまとめられますが、この機能が原因でILの読み込みやJITコンパイルに問題が生じることがあります。

特にネイティブライブラリのロードや動的コード生成を行う場合は、PublishSingleFileの設定を見直し、必要に応じてIncludeAllContentForSelfExtractSelfContainedのオプションを調整してください。

NuGet依存関係の点検

バージョン競合の検出

複数のNuGetパッケージが異なるバージョンの同一依存ライブラリを参照していると、実行時に不整合が生じてInvalidProgramExceptionが発生することがあります。

これを「依存関係の競合」と呼びます。

Visual Studioの「依存関係」ツリーやdotnet list package --include-transitiveコマンドで依存関係を確認し、バージョンを統一することが重要です。

bindingRedirectの設定も適切に行いましょう。

参照外れDLLの削除

プロジェクトに不要なDLLや古いバージョンのDLLが混在していると、実行時に誤ったDLLが読み込まれ、ILの不整合が起きることがあります。

これによりInvalidProgramExceptionが発生します。

ビルド出力フォルダをクリーンし、binobjフォルダを削除してから再ビルドすることが推奨されます。

また、NuGetパッケージの再インストールも効果的です。

ソースコードレベルのチェック

unsafeコードとポインタ演算

unsafeキーワードを使ったポインタ操作やアンマネージドコードとの連携は、ILコードの整合性を崩しやすい箇所です。

ポインタの型不一致や不正なメモリアクセスがあると、JITコンパイル時にInvalidProgramExceptionが発生することがあります。

コードレビューや静的解析ツールを使い、unsafeコードの安全性と正当性を確認してください。

ジェネリック制約の誤用

ジェネリック型やメソッドの制約を誤って設定すると、ILコードの型安全性が損なわれ、実行時に例外が発生することがあります。

特に複雑なジェネリックのネストや型パラメータの不整合が原因です。

ジェネリックの制約を見直し、必要に応じて単純化することが効果的です。

CI/CD環境での再現テスト

コンテナビルドでの差分検証

CI/CDパイプラインでコンテナを使ったビルド環境を構築し、ローカル環境との違いを検証します。

環境差異によるInvalidProgramExceptionの発生を防ぐため、ビルド環境をコード化し、再現性を確保します。

Dockerfileやビルドスクリプトを用いて、依存関係やSDKバージョンを固定し、問題の切り分けに役立てます。

マルチアーキテクチャテスト

x86、x64、ARMなど複数のプラットフォームでビルド・テストを自動化し、プラットフォーム依存の問題を早期に検出します。

これにより、プラットフォーム不一致によるInvalidProgramExceptionの発生を未然に防げます。

GitHub ActionsやAzure PipelinesなどのCIツールでマルチプラットフォームビルドを設定し、継続的に検証することが推奨されます。

主な対処法

ビルド設定の統一

Prefer32Bitフラグの扱い

Visual Studioのプロジェクト設定にあるPrefer 32-bitフラグは、AnyCPUターゲットでビルドした場合に64ビット環境でも32ビットモードで実行するかどうかを制御します。

この設定が原因で、64ビット環境で32ビットDLLを読み込もうとしてInvalidProgramExceptionが発生することがあります。

対処法としては、64ビット環境で動作させる場合はPrefer 32-bitのチェックを外し、64ビットネイティブで動作させることが推奨されます。

逆に32ビット環境で動作させる場合はチェックを入れたままにします。

環境に合わせて適切に設定し、ビルド構成を統一することが重要です。

PlatformTargetの選定指針

PlatformTargetは、ビルド時に生成されるアセンブリの対象プラットフォームを指定します。

AnyCPUx86x64などがありますが、実行環境や依存DLLのビット数に合わせて選定しなければなりません。

  • 依存DLLが32ビットのみの場合はx86を選択
  • 64ビット環境で動作させる場合はx64を選択
  • 汎用的に動作させたい場合はAnyCPUを選択し、Prefer 32-bitの設定を調整

ビルド構成を複数用意する場合は、CI/CDでビルドごとに適切な設定を使い分けることが望ましいです。

破損DLLの置き換え

クリーンリストア

破損したDLLや不整合な依存関係が原因でInvalidProgramExceptionが発生する場合、プロジェクトのクリーンリストアを行います。

Visual Studioの「ビルド」メニューから「クリーン」を実行し、binobjフォルダを手動で削除してから再ビルドします。

また、NuGetパッケージの再インストールも効果的です。

dotnet restoreコマンドやVisual Studioのパッケージマネージャーでパッケージを再取得し、破損したDLLを置き換えます。

キャッシュクリア方法

NuGetのローカルキャッシュが破損している場合も問題が発生します。

以下のコマンドでキャッシュをクリアし、再度パッケージを取得してください。

dotnet nuget locals all --clear

Visual Studioの「ツール」→「オプション」→「NuGetパッケージマネージャー」からもキャッシュのクリアが可能です。

キャッシュクリア後は必ずプロジェクトを再ビルドしてください。

コンパイラおよびランタイムの更新

.NET SDKのバージョン固定

コンパイラやランタイムのバージョン差異が原因でILコードの生成やJITコンパイルに問題が生じることがあります。

特に複数の開発者やCI環境でバージョンが異なる場合は注意が必要です。

global.jsonファイルをプロジェクトルートに配置し、使用する.NET SDKのバージョンを固定することで、ビルド環境の一貫性を保てます。

{
  "sdk": {
    "version": "7.0.100"
  }
}

Visual Studioの最新化

Visual Studioのバージョンが古いと、コンパイラやビルドツールにバグが残っている可能性があります。

最新の安定版にアップデートし、バグ修正や機能改善を取り込むことが推奨されます。

また、拡張機能やツールチェーンも最新に保つことで、ビルド時の問題を減らせます。

動的IL生成の修正

LocalBuilderの型整合性

System.Reflection.Emitを使った動的IL生成では、LocalBuilderで宣言したローカル変数の型と、実際に代入する値の型が一致している必要があります。

型不整合があるとInvalidProgramExceptionが発生します。

以下のサンプルは正しい型整合性の例です。

using System;
using System.Reflection.Emit;
class Program
{
    static void Main()
    {
        DynamicMethod method = new DynamicMethod("Hello", null, null);
        ILGenerator il = method.GetILGenerator();
        // int型のローカル変数を宣言
        LocalBuilder local = il.DeclareLocal(typeof(int));
        // int型の値を代入
        il.Emit(OpCodes.Ldc_I4, 42);
        il.Emit(OpCodes.Stloc, local);
        il.Emit(OpCodes.Ret);
        var action = (Action)method.CreateDelegate(typeof(Action));
        action();
        Console.WriteLine("動的IL生成が成功しました。");
    }
}
動的IL生成が成功しました。

型の不一致がないか、ILコードを丁寧に確認してください。

例外処理ブロックの正当性

動的IL生成で例外処理(try-catch-finally)を実装する場合、例外ブロックの開始・終了位置やネスト構造が正確でなければなりません。

不正な例外処理ブロックはJITコンパイラでエラーとなり、InvalidProgramExceptionを引き起こします。

ILGeneratorのBeginExceptionBlockBeginCatchBlockEndExceptionBlockの呼び出し順序を厳密に守り、例外処理の構造を正しく構築してください。

オブファスケーション設定の見直し

フロー制御変換の無効化

オブファスケーションツールの中には、コントロールフローを複雑化する「フロー制御変換」機能がありますが、これが原因でILコードが破損しInvalidProgramExceptionが発生することがあります。

問題が疑われる場合は、オブファスケーションの設定でフロー制御変換を無効化し、例外が解消されるか確認してください。

属性保持ルールの追加

オブファスケーション時に特定の属性やメタデータが削除されると、ILコードの整合性が崩れることがあります。

重要な属性(例:MethodImplAttributeCompilerGeneratedAttribute)は保持するように設定を追加してください。

これにより、JITコンパイル時の不整合を防げます。

AOT / モバイル環境での調整

IL2CPPのブラックリスト登録

UnityのIL2CPP変換時に、特定のメソッドや型が変換対象から除外されていると、実行時にInvalidProgramExceptionが発生します。

link.xmlファイルで必要な型やメソッドをブラックリストに登録し、変換から除外することで回避可能です。

<linker>
  <assembly fullname="AssemblyName">
    <type fullname="Namespace.TypeName" preserve="all" />
  </assembly>
</linker>

Linker設定での除外

XamarinやUnityのLinker(コードストリッピング)機能が過剰に働くと、必要なコードが削除されてILの整合性が崩れます。

Linkerの設定で除外リストを作成し、重要なコードを保持してください。

Visual Studioのプロジェクト設定やlinker.xmlで除外設定を行い、ストリッピングの影響を最小限に抑えます。

再リリース前の検証スクリプト

ILVerifyのCI組み込み

ILVerifyをCIパイプラインに組み込み、ビルド時にILコードの整合性を自動検査します。

これにより、InvalidProgramExceptionの原因となるIL破損を早期に検出し、リリース前に修正できます。

GitHub ActionsやAzure PipelinesでILVerifyを実行するスクリプトを作成し、継続的に品質を担保しましょう。

P/Invokeシグネチャテスト

P/InvokeでネイティブDLLを呼び出す場合、シグネチャの不整合がInvalidProgramExceptionを引き起こすことがあります。

テストコードを用意し、すべてのP/Invoke呼び出しが正しく動作するか検証してください。

特に引数の型や呼び出し規約、文字列のマシュリング設定を厳密にチェックし、問題があれば修正します。

予防策とベストプラクティス

プラットフォーム別ビルドの自動化

プラットフォームごとに異なるビルド設定を手動で管理すると、設定ミスやビット数の不整合が発生しやすくなります。

これを防ぐために、ビルドプロセスを自動化し、プラットフォーム別のビルドを確実に行うことが重要です。

たとえば、CI/CDパイプラインでx86x64AnyCPUなど複数のビルド構成を用意し、それぞれの環境に適したビルドを自動的に生成・テストします。

Azure PipelinesやGitHub ActionsなどのCIツールを活用し、ビルドスクリプトにdotnet buildコマンドの-r(ランタイム識別子)や-p:Platformオプションを組み込むと効果的です。

こうした自動化により、プラットフォーム間の不整合を早期に検出でき、InvalidProgramExceptionの発生リスクを減らせます。

依存ライブラリのチェックリスト

依存ライブラリのバージョンや整合性が乱れると、ILコードの不整合や実行時エラーの原因になります。

依存関係を管理するために、チェックリストを作成し、以下のポイントを定期的に確認します。

  • すべての依存ライブラリのバージョンが統一されているか
  • 署名付きアセンブリの署名が有効か
  • 不要な依存ライブラリが含まれていないか
  • NuGetパッケージのキャッシュが破損していないか
  • 依存関係のトランジティブなバージョン競合がないか

このチェックリストをCIの一部として組み込み、依存関係の問題を早期に発見・修正することが推奨されます。

動的コード生成のユニットテスト

動的にILコードを生成する処理は、InvalidProgramExceptionの原因になりやすいため、ユニットテストを充実させることが重要です。

生成したコードが正しく動作するか、型の整合性や例外処理の構造が正しいかをテストで検証します。

たとえば、Reflection.EmitやExpression Treeを使う部分に対しては、生成コードの実行テストやIL検証ツールを組み合わせてテストケースを作成します。

異常系のテストも含め、例外が発生しないことを確認することで、問題の早期発見につながります。

CIでの定期的な静的IL検査

CI環境にILVerifyなどの静的IL検査ツールを組み込み、ビルドごとにILコードの整合性をチェックします。

これにより、ILの破損や不正な命令が含まれていないかを自動的に検出でき、InvalidProgramExceptionの原因を未然に防げます。

また、IL検査の結果をレポートとして出力し、開発チームで共有することで品質管理を強化できます。

CIでの自動検査は、リリース前の品質保証に欠かせないプロセスです。

ユーザー環境別クラッシュログ収集

実際のユーザー環境で発生するInvalidProgramExceptionは、環境依存の問題であることが多いため、クラッシュログの収集と分析が重要です。

クラッシュレポートツール(例:App Center、Sentry、Raygunなど)を導入し、例外発生時のスタックトレースや環境情報を収集します。

収集したログをもとに、特定のOSバージョンやCPUアーキテクチャ、インストールされている依存DLLのバージョンなどを分析し、問題の再現条件を特定します。

これにより、環境依存のInvalidProgramExceptionを効率的に解決できます。

まとめ

この記事では、C#で発生するInvalidProgramExceptionの原因や特徴、代表的なスタックトレース例を紹介しました。

発生原因はコンパイル時のIL破損やプラットフォーム不一致、動的コード生成の問題など多岐にわたります。

調査にはIL解析ツールやJITデバッグオプションが有効で、ビルド設定の統一や破損DLLの置き換え、コンパイラの更新などが主な対処法です。

さらに、予防策としてプラットフォーム別ビルドの自動化や依存関係の管理、CIでの静的IL検査を推奨します。

これらを実践することで、InvalidProgramExceptionの発生を抑え、安定したアプリケーション開発が可能になります。

関連記事

Back to top button