【C#】MissingMethodExceptionの原因と解決策:ライブラリのバージョン差異からメソッドシグネチャ変更まで
MissingMethodException
は実行時に呼び出そうとしたメソッドのシグネチャが見つからないときに発生します。
ライブラリのバージョン差異、メソッド名や引数変更、アクセス修飾子の変更が主因です。
参照DLLをそろえ再ビルドし、古いアセンブリを削除すれば多くのケースで解消できます。
MissingMethodExceptionとは
C#におけるMissingMethodException
は、実行時に呼び出そうとしたメソッドが見つからない場合にスローされる例外です。
これは、プログラムの実行中に特定のメソッドが存在しない、またはアクセスできない状況が発生したことを示しています。
主に、ライブラリのバージョン違いやメソッドのシグネチャ変更など、呼び出し元と呼び出し先の不整合が原因で起こります。
この例外は、.NET Frameworkや.NET Core、.NET 5以降の環境すべてで共通して発生する可能性があり、特に複数のプロジェクトや外部ライブラリを組み合わせて開発している場合に注意が必要です。
MissingMethodException
が発生すると、通常はプログラムがクラッシュしたり、特定の機能が動作しなくなったりしますので、原因の特定と修正が重要です。
例外の位置付けと基底クラス
MissingMethodException
は、System.MissingMethodException
クラスとして.NETの標準ライブラリに用意されています。
この例外は、System.MissingMemberException
の派生クラスであり、さらにその基底クラスはSystem.MemberAccessException
です。
例外の継承階層は以下のようになっています。
クラス名 | 説明 |
---|---|
System.Exception | すべての例外の基底クラス |
System.SystemException | システムレベルの例外の基底クラス |
System.MemberAccessException | メンバーへのアクセスに関する例外の基底クラス |
System.MissingMemberException | メンバーが見つからない例外の基底クラス |
System.MissingMethodException | メソッドが見つからない場合の例外 |
この階層からわかるように、MissingMethodException
はメソッド呼び出しに特化した例外であり、メソッドが存在しない、またはアクセスできない場合にスローされます。
例えば、リフレクションでメソッドを呼び出す際に該当メソッドが見つからない場合や、実行時に異なるバージョンのアセンブリが読み込まれている場合などに発生します。
MissingMethodException
はチェック例外ではなく、実行時例外(ランタイム例外)です。
そのため、コンパイル時には検出されず、実行時に初めて問題が顕在化します。
これが原因で、開発中は問題が見えにくく、リリース後に発生することも少なくありません。
発生時の典型メッセージ
MissingMethodException
が発生すると、例外メッセージには通常、呼び出そうとしたメソッドの名前やシグネチャ(引数の型や数)が含まれます。
これにより、どのメソッドが見つからなかったのかを特定しやすくなっています。
典型的な例外メッセージの例を以下に示します。
System.MissingMethodException: メソッド 'Namespace.ClassName.MethodName(Type1, Type2)' が見つかりません。
または英語環境では、
System.MissingMethodException: Method 'Namespace.ClassName.MethodName(Type1, Type2)' not found.
このメッセージは、呼び出そうとしたメソッドの完全修飾名(名前空間+クラス名+メソッド名)と、引数の型情報が含まれているため、どのメソッドが問題なのかを正確に把握できます。
スタックトレースも一緒に表示されることが多く、例外が発生したコードの呼び出し履歴を追うことが可能です。
これにより、どのコードから問題のメソッドが呼び出されたのかを特定しやすくなります。
例えば、以下のようなスタックトレースが表示されることがあります。
場所 Namespace.ClassName.MethodName(Type1, Type2)
場所 Program.Main(String[] args)
この情報をもとに、呼び出し元のコードや参照しているライブラリのバージョンを確認し、問題の原因を探ることができます。
以上のように、MissingMethodException
は実行時にメソッドが見つからないことを示す例外であり、基底クラスの階層や例外メッセージの特徴を理解することで、原因の特定や対処がしやすくなります。
代表的な発生原因
ライブラリのバージョン差異
DLLの複数バージョン混在
同じ名前のDLLが複数のバージョンでプロジェクトや実行環境に存在すると、実行時にどのバージョンが読み込まれるかが不確定になります。
例えば、プロジェクトAがバージョン1.0のライブラリを参照し、プロジェクトBがバージョン1.1を参照している場合、実行時に古いバージョンのDLLが読み込まれると、追加されたメソッドが存在しないためMissingMethodException
が発生します。
この問題は、ビルド出力フォルダに複数のバージョンのDLLが混在している場合や、GAC(グローバルアセンブリキャッシュ)に異なるバージョンが登録されている場合に起こりやすいです。
特に、手動でDLLをコピーしたり、古いDLLが残っていると発生します。
NuGet自動更新によるズレ
NuGetパッケージの自動更新機能を利用していると、依存関係のバージョンが意図せず変わることがあります。
例えば、あるパッケージが最新バージョンに更新され、メソッドのシグネチャが変更された場合、更新前のバージョンでビルドされたコードが新しいDLLを参照するとMissingMethodException
が発生します。
また、複数のプロジェクトで異なるバージョンのパッケージを参照していると、ビルド時にバージョンの不整合が起きやすくなります。
これを防ぐには、packages.config
やPackageReference
でバージョンを明示的に固定し、CI/CDパイプラインで依存関係の整合性をチェックすることが重要です。
メソッドシグネチャの変更
パラメータ追加・削除
メソッドのパラメータを追加したり削除した場合、呼び出し元のコードが再コンパイルされていないと、実行時に呼び出そうとしたシグネチャが存在しないためMissingMethodException
が発生します。
例えば、以下のようにメソッドを変更したケースです。
public class SampleClass
{
// 変更前
public void PrintMessage(string message)
{
Console.WriteLine(message);
}
// 変更後
public void PrintMessage(string message, int count)
{
for (int i = 0; i < count; i++)
{
Console.WriteLine(message);
}
}
}
呼び出し元が古いバージョンのPrintMessage(string)
を呼んでいると、実行時にPrintMessage(string, int)
が見つからず例外が発生します。
デフォルト引数の導入
C#のデフォルト引数はコンパイル時に呼び出し元のコードに展開されるため、メソッドにデフォルト引数を追加しても呼び出し元を再コンパイルしないとMissingMethodException
が発生します。
例えば、
public class SampleClass
{
// 変更前
public void Foo(int a) { Console.WriteLine(a); }
// 変更後
public void Foo(int a, string b = "default") { Console.WriteLine($"{a}, {b}"); }
}
呼び出し元が再コンパイルされていないと、Foo(5)
の呼び出しがFoo(int)
を期待してしまい、実際にはFoo(int, string)
しか存在しないため例外が発生します。
オーバーロードが多すぎる場合
同じ名前のメソッドが多数オーバーロードされていると、呼び出し時にどのシグネチャが呼ばれるかが複雑になり、誤ったシグネチャを呼び出すことでMissingMethodException
が発生することがあります。
特にリフレクションや動的呼び出しで起こりやすいです。
アクセス修飾子の変更
public→internal の影響
メソッドのアクセス修飾子をpublic
からinternal
やprivate
に変更すると、外部アセンブリやクラスからのアクセスが制限されます。
呼び出し元がアクセスできないメソッドを呼ぼうとすると、MissingMethodException
が発生します。
例えば、ライブラリのメソッドをpublic
からinternal
に変更した場合、別プロジェクトからそのメソッドを呼び出すと例外が発生します。
アクセス修飾子の変更はAPIの互換性に大きく影響するため、慎重に行う必要があります。
条件付きコンパイル
#if で除外されたメソッド
#if
ディレクティブで特定のメソッドがコンパイルから除外されている場合、実行時にそのメソッドが存在しないためMissingMethodException
が発生します。
例えば、以下のようなコードでDEBUG
シンボルが定義されていないときにメソッドが存在しません。
#if DEBUG
public void DebugOnlyMethod() { Console.WriteLine("Debug mode"); }
#endif
呼び出し元がDebugOnlyMethod
を呼んでいると、リリースビルドで例外が発生します。
リフレクション呼び出し
MethodInfo取得失敗のパターン
リフレクションでメソッドを取得する際に、メソッド名やパラメータの型を間違えるとMethodInfo
がnull
になり、呼び出し時にMissingMethodException
が発生します。
var method = typeof(SampleClass).GetMethod("NonExistentMethod");
method.Invoke(null, null); // ここで例外発生
パラメータの型やメソッドのオーバーロードを正確に指定しないと、正しいメソッドが取得できません。
DynamicキーワードとDLR
実行時バインディング失敗
dynamic
キーワードを使った動的呼び出しは、実行時にメソッドの存在をチェックします。
呼び出そうとしたメソッドが存在しない場合、RuntimeBinderException
が発生しますが、内部的にMissingMethodException
が原因となることもあります。
例えば、以下のコードでNonExistentMethod
を呼ぶと例外が発生します。
dynamic obj = new SampleClass();
obj.NonExistentMethod();
動的型付けは便利ですが、メソッドの存在をコンパイル時に保証できないため、例外が発生しやすいです。
XAMLバインディング
WPF・UWPでのDataContext依存
WPFやUWPのXAMLでバインディングしているプロパティやメソッドが、DataContextの型変更やバージョン違いで存在しなくなると、MissingMethodException
が発生することがあります。
例えば、ViewModelのメソッドを削除したのにXAMLのバインディングが更新されていない場合、実行時に例外が起きます。
XAMLのバインディングは静的解析が難しいため、こうした問題が起こりやすいです。
プラットフォーム依存ビルド
AnyCPU vs x64/x86
ビルド構成がAnyCPU
とx64
やx86
で混在していると、異なるプラットフォーム用のDLLが読み込まれ、メソッドが見つからないことがあります。
特にネイティブコードと連携する場合に注意が必要です。
例えば、64ビット環境で32ビットDLLを読み込もうとすると、MissingMethodException
やBadImageFormatException
が発生することがあります。
アセンブリのロード順序
Shadow Copyとプラグイン構成
アプリケーションがプラグインや拡張機能を動的に読み込む場合、Shadow Copy機能やロード順序の問題で古いDLLが読み込まれることがあります。
これにより、期待するメソッドが存在しないためMissingMethodException
が発生します。
特にテストフレームワークやWebアプリケーションでよく見られる問題です。
強名アセンブリとGAC
Strong Name mismatch
強名付きアセンブリ(Strong-Named Assembly)をGACに登録している場合、バージョンやパブリックキーが異なると、実行時に異なるアセンブリが読み込まれ、メソッドが見つからないことがあります。
この場合、MissingMethodException
が発生しやすく、GACの登録状況やバージョン管理を厳密に行う必要があります。
IL Rewritingやリンカ
Unusedコードのストリップ
IL Rewritingツールやリンカ(トリマー)が未使用のメソッドを削除する際に、誤って必要なメソッドを削除してしまうと、実行時にMissingMethodException
が発生します。
特に、リフレクションや動的呼び出しで使用されるメソッドは静的解析で検出されにくいため、トリミング設定を適切に行うことが重要です。
例えば、[Preserve]
属性やlink.xml
ファイルで明示的に残すメソッドを指定します。
例外メッセージの読み解き方
フルネームとシグネチャ解析
MissingMethodException
の例外メッセージには、通常、呼び出そうとしたメソッドのフルネーム(名前空間+クラス名+メソッド名)とシグネチャ(引数の型や数)が含まれています。
この情報は、どのメソッドが見つからなかったのかを特定するために非常に重要です。
例えば、以下のような例外メッセージが表示されます。
System.MissingMethodException: メソッド 'MyNamespace.MyClass.MyMethod(System.Int32, System.String)' が見つかりません。
このメッセージからは、
- 名前空間:
MyNamespace
- クラス名:
MyClass
- メソッド名:
MyMethod
- 引数の型:
System.Int32
(int型)、System.String
(string型)
がわかります。
この情報をもとに、呼び出し元のコードや参照しているライブラリのメソッド定義と照合します。
特に、引数の型や数が異なっている場合は、メソッドシグネチャの不一致が原因であることが多いです。
また、オーバーロードされたメソッドが複数ある場合は、正確なシグネチャを確認することで、どのバージョンのメソッドが呼び出されているかを判断できます。
スタックトレースで注目する行
例外が発生すると、通常はスタックトレース(呼び出し履歴)が表示されます。
スタックトレースは、例外が発生した場所から呼び出し元までのメソッドの連なりを示しており、問題の原因箇所を特定する手がかりになります。
スタックトレースの中で特に注目すべきは、
- 最初に
MissingMethodException
がスローされた行 - 自分のコード(アプリケーションコード)が含まれる行
です。
例えば、以下のようなスタックトレースがあった場合、
場所 MyNamespace.MyClass.MyMethod(Int32, String)
場所 MyApp.Program.Main(String[] args)
MyMethod
の呼び出しで例外が発生し、Main
メソッドから呼ばれていることがわかります。
これにより、MyMethod
の定義や参照しているライブラリのバージョンを重点的に確認できます。
また、スタックトレースに外部ライブラリの名前やファイルパスが含まれている場合は、そのライブラリのバージョンやビルド状況も確認するとよいでしょう。
Fusionログの有効化方法
.NET Framework環境でアセンブリのバインド問題を調査する際に役立つのがFusionログです。
Fusionログは、アセンブリのロード時にどのファイルが読み込まれたか、どのパスを探したかを詳細に記録します。
これにより、誤ったバージョンのDLLが読み込まれているかどうかを確認できます。
Fusionログを有効にする手順は以下の通りです。
- レジストリエディタを開く
regedit
を起動します。
- Fusionログの設定を追加する
以下のパスに移動します。
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Fusion
もしFusion
キーがなければ作成します。
- 以下のDWORD値を追加・設定する
EnableLog
:1
(ログを有効化)ForceLog
:1
(強制的にログを記録)LogFailures
:1
(失敗したバインドもログに記録)LogResourceBinds
:1
(リソースバインドもログに記録)
- ログの出力先を指定する
LogPath
(文字列値)を作成し、ログを保存したいフォルダのパスを指定します。
例:C:\FusionLogs\
- アプリケーションを再起動して問題を再現する
- ログファイルを確認する
指定したフォルダに.htm
形式のログファイルが生成されます。
これをブラウザで開くと、どのアセンブリがどこから読み込まれたか、失敗した場合はその原因が詳細に記録されています。
Fusionログは.NET Framework専用の機能であり、.NET Coreや.NET 5以降では利用できません。
その場合は、AssemblyLoadContext
のイベントを利用したり、dotnet-trace
などのツールを使って調査します。
Fusionログを活用することで、MissingMethodException
の原因となるアセンブリのバージョン不一致やロード失敗を特定しやすくなります。
再現手順の特定
最小構成プロジェクトの作成
MissingMethodException
の原因を特定するためには、問題を再現できる最小限のプロジェクトを作成することが非常に効果的です。
複雑なプロジェクトや多くの依存関係が絡んでいる場合、問題の切り分けが難しくなるため、まずは問題の本質に絞ったシンプルな環境を用意します。
具体的には、以下の手順で進めます。
- 新規プロジェクトの作成
Visual Studioやdotnet new
コマンドで新しいコンソールアプリケーションやクラスライブラリを作成します。
- 問題のメソッド呼び出しを再現するコードを記述
問題が発生しているメソッドの呼び出しだけを含むコードを書きます。
例えば、特定のライブラリのメソッドを呼び出すだけの簡単なコードにします。
- 依存関係を最小限に絞る
問題のライブラリだけを参照し、他の不要なパッケージやプロジェクト参照は外します。
- ビルドと実行で例外が発生するか確認
例外が再現できれば、問題の切り分けが成功しています。
再現しない場合は、依存関係や設定を少しずつ追加しながら再現条件を探ります。
この最小構成プロジェクトを使うことで、問題の原因がライブラリのバージョン違いなのか、メソッドシグネチャの不一致なのか、あるいはビルド設定の問題なのかを効率的に調査できます。
バージョン固定とロールバック
MissingMethodException
はライブラリのバージョン差異が原因で発生することが多いため、依存しているパッケージやDLLのバージョンを固定し、問題が発生しない安定したバージョンにロールバックすることが有効です。
具体的な手順は以下の通りです。
- 現在のバージョンを確認
csproj
ファイルやpackages.config
、PackageReference
の設定を確認し、参照しているパッケージのバージョンを把握します。
- バージョンを明示的に固定
バージョン範囲指定を外し、特定のバージョンに固定します。
例えば、<PackageReference Include="ExampleLib" Version="1.2.3" />
のように記述します。
- ロールバックの検討
問題が発生し始めたバージョンより前の安定版に戻してビルド・実行し、例外が発生しないか確認します。
- 依存関係の整合性を保つ
複数のプロジェクトが異なるバージョンを参照している場合は、全体でバージョンを統一します。
- バージョン管理ツールの活用
Gitなどのバージョン管理システムで、問題が発生する前のコミットに戻して動作確認を行うことも有効です。
バージョン固定とロールバックにより、どのバージョンから問題が発生したのかを特定しやすくなり、原因の切り分けが進みます。
CI環境での再現確認
継続的インテグレーション(CI)環境でMissingMethodException
の再現を確認することは、問題の早期発見と安定したビルドの維持に役立ちます。
CI環境での再現確認のポイントは以下の通りです。
- CIビルドの設定を最新に保つ
依存パッケージのバージョンやビルド設定をCIのビルドスクリプトに明示的に記述し、ローカル環境と同じ条件でビルドできるようにします。
- 最小構成プロジェクトをCIに組み込む
問題の再現に成功した最小構成プロジェクトをCIのテストスイートに追加し、例外が発生するか自動で検証します。
- バージョン管理と連携
依存パッケージのバージョンを固定し、CIでのビルドが成功するかを常にチェックします。
バージョンアップ時には自動テストで例外の有無を確認します。
- ログの収集と通知設定
CIで例外が発生した場合に詳細なログを収集し、開発チームに通知する仕組みを整えます。
- ロールバックや修正の検証
問題の修正やバージョンダウンを行った際に、CIで再度ビルド・テストを実行し、問題が解消されたかを確認します。
CI環境での再現確認を行うことで、開発チーム全体で問題の発生を早期に把握でき、安定したリリースを維持しやすくなります。
デバッグアプローチ
Fusionログビューア
Fusionログは、.NET Frameworkでアセンブリのバインド(読み込み)に関する詳細な情報を記録する機能です。
MissingMethodException
の原因がアセンブリのバージョン不一致やロード失敗にある場合、Fusionログを解析することで問題の特定が容易になります。
Assembly Bind Failure Viewerの使い方
Assembly Bind Failure Viewer(通称:Fuslogvw.exe)は、Fusionログを視覚的に確認できるMicrosoft公式ツールです。
以下の手順で使用します。
- ツールの起動
Visual Studioの開発者コマンドプロンプトやWindowsのスタートメニューからFuslogvw.exe
を起動します。
通常はC:\Program Files (x86)\Microsoft SDKs\Windows\<バージョン>\bin\NETFX <version> Tools\
にあります。
- ログの設定
ツールのメニューから「Settings」を開き、「Log bind failures to disk」にチェックを入れます。
必要に応じて「Enable custom log path」を指定し、ログの保存先を設定します。
- 問題の再現
アプリケーションを実行し、MissingMethodException
が発生する状況を再現します。
- ログの確認
Fuslogvwに戻り、「Refresh」ボタンを押すと、最近のバインド失敗ログが一覧表示されます。
該当するログをダブルクリックすると詳細が表示されます。
- ログ内容の解析
ログには、どのアセンブリがどのパスから読み込まれたか、どのパスを探したか、失敗の原因が記録されています。
これにより、誤ったバージョンのDLLが読み込まれているか、ファイルが見つからないかなどを判断できます。
- ログの無効化
調査終了後は、Fusionログを無効化してパフォーマンスへの影響を防ぎます。
Fusionログビューアを使うことで、アセンブリのロード問題を視覚的に把握でき、MissingMethodException
の根本原因を特定しやすくなります。
ildasm・ILSpyでの検証
MissingMethodException
は、実行時に呼び出そうとしたメソッドが存在しないことが原因です。
メソッドが本当にアセンブリ内に存在するかどうかを確認するには、IL(中間言語)レベルでの検証が有効です。
ildasm
(IL Disassembler)やILSpy
は、.NETアセンブリのILコードを閲覧できるツールです。
ILレベルでのメソッド存在確認
- ildasmの使用方法
- Visual Studioの開発者コマンドプロンプトで
ildasm
を起動します - 問題のDLLやEXEファイルを開きます
- ツリー形式で名前空間やクラス、メソッドが表示されるので、該当のメソッド名を探します
- メソッドが存在しない場合は、呼び出し元のコードとアセンブリの不整合が疑われます
- ILSpyの使用方法
- ILSpyはGUIベースのオープンソースツールで、より使いやすいです
- DLLやEXEをドラッグ&ドロップで開き、クラスやメソッドを検索できます
- メソッドのシグネチャや実装コードも閲覧可能です
- メソッドのシグネチャ確認
- メソッド名だけでなく、引数の型や戻り値の型も確認します
- 呼び出し元のコードと完全に一致しているかをチェックします
- バージョン違いの検出
- 期待しているメソッドが存在しない場合、参照しているDLLのバージョンが異なる可能性が高いです
ILレベルでの検証は、ソースコードが手元にない外部ライブラリの問題を調査する際に特に有効です。
Unit Testによる自動検出
MissingMethodException
の発生を未然に防ぐために、ユニットテストでAPI互換性を自動的に検出する方法があります。
これにより、メソッドの削除やシグネチャ変更があった場合にテストが失敗し、問題を早期に発見できます。
互換性テストのサンプル構成
以下は、特定のメソッドが存在するかをリフレクションでチェックする簡単なユニットテストの例です。
using System;
using System.Reflection;
using Xunit;
public class ApiCompatibilityTests
{
[Fact]
public void Test_MethodExists()
{
// 対象の型を取得
Type targetType = typeof(MyLibrary.MyClass);
// メソッド名とパラメータの型を指定して取得
MethodInfo method = targetType.GetMethod("MyMethod", new Type[] { typeof(int), typeof(string) });
// メソッドが存在するかを検証
Assert.NotNull(method);
}
}
// テストが成功すればメソッドは存在する
// 失敗するとMissingMethodExceptionの原因となるメソッドの欠如を検出できる
このテストは、MyLibrary.MyClass
にMyMethod(int, string)
が存在するかを確認します。
メソッドが存在しなければテストが失敗し、問題を早期に検知できます。
このような互換性テストをCIパイプラインに組み込むことで、APIの破壊的変更を防ぎ、MissingMethodException
の発生リスクを低減できます。
さらに、複数のメソッドやクラスに対して同様のテストを作成し、網羅的にチェックすることも可能です。
解決策
DLLバージョンを固定する
MissingMethodException
の多くは、異なるバージョンのDLLが混在することによって発生します。
バージョンを固定し、依存関係の整合性を保つことが重要です。
BindingRedirectの設定方法
.NET Frameworkでは、異なるバージョンのアセンブリを参照している場合にBindingRedirect
を使ってバージョンの不整合を解消できます。
app.config
やweb.config
に以下のように記述します。
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="ExampleLibrary" publicKeyToken="32ab4ba45e0a69a1" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
この設定により、ExampleLibrary
のバージョン0.0.0.0から2.0.0.0までの呼び出しはすべてバージョン2.0.0.0にリダイレクトされます。
これで異なるバージョンのDLLが混在しても、実行時に統一されたバージョンが読み込まれ、MissingMethodException
の発生を防げます。
PackageReferenceでのバージョンピン
.NET Coreや.NET 5以降のプロジェクトでは、PackageReference
でNuGetパッケージのバージョンを明示的に固定(ピン留め)することが推奨されます。
csproj
ファイルに以下のように記述します。
<ItemGroup>
<PackageReference Include="ExampleLibrary" Version="2.0.0" />
</ItemGroup>
バージョンを固定することで、ビルド時に意図しないバージョンアップデートを防ぎ、依存関係の不整合を減らせます。
CI/CDパイプラインでもバージョン固定は重要なポイントです。
全プロジェクトを再ビルド
複数プロジェクトが絡む場合、古いDLLや中間ファイルが残っているとMissingMethodException
が発生しやすくなります。
全プロジェクトをクリーンビルドすることが効果的です。
CleanとRebuildの効果
- Clean
ビルド出力フォルダbin
やobj
のファイルをすべて削除します。
これにより、古いDLLや中間ファイルが残らず、クリーンな状態になります。
- Rebuild
Cleanの後に全プロジェクトをビルドし直します。
依存関係も含めて最新の状態でビルドされるため、DLLのバージョン不整合やメソッドの不一致を防げます。
Visual Studioでは「ビルド」メニューから「クリーン」と「リビルド」を選択できます。
コマンドラインではdotnet clean
とdotnet build
を使います。
メソッド互換性を保つ実装
APIの変更時に互換性を保つことで、呼び出し元のコードが古いメソッドを呼んでも例外が発生しないようにできます。
Obsolete属性による段階的廃止
メソッドを廃止する際は、すぐに削除せずに[Obsolete]
属性を付けて警告を出す方法があります。
[Obsolete("Use NewMethod instead.")]
public void OldMethod()
{
// 旧メソッドの実装
}
これにより、呼び出し元に移行を促しつつ、すぐにMissingMethodException
が発生するのを防げます。
段階的に新しいメソッドへ切り替えられます。
ラッパーメソッドでの後方互換
メソッドシグネチャを変更する場合、古いシグネチャのメソッドを残し、新しいメソッドを内部で呼び出すラッパーを作る方法もあります。
public void Foo(int a)
{
Foo(a, "default");
}
public void Foo(int a, string b)
{
// 新しい実装
}
これにより、古い呼び出しも動作し続け、MissingMethodException
を防止できます。
アクセス修飾子を適切に選ぶ
アクセス修飾子の変更はMissingMethodException
の原因になりやすいので、API設計時に慎重に設定します。
InternalsVisibleTo活用
internal
メソッドをテストプロジェクトや特定のアセンブリからアクセス可能にしたい場合、InternalsVisibleTo
属性を使います。
[assembly: InternalsVisibleTo("MyProject.Tests")]
これにより、internal
メソッドを外部から呼び出せるようにしつつ、外部公開は制限できます。
アクセス制御を適切に行い、例外発生を防ぎます。
コードの後方互換ポリシー
APIの変更は後方互換性を意識して行うことが重要です。
SemVerでのAPI管理
セマンティックバージョニング(SemVer)を採用し、メジャーバージョンアップ時に破壊的変更を行うルールを設けます。
バージョン | 意味 |
---|---|
MAJOR | 破壊的変更(互換性なし) |
MINOR | 後方互換の機能追加 |
PATCH | バグ修正 |
これにより、呼び出し元はメジャーバージョンアップを警戒し、破壊的変更に備えられます。
MissingMethodException
の発生リスクを減らせます。
リフレクション呼び出しを安全に
リフレクションでメソッドを呼び出す際は、名前のハードコーディングを避け、型安全に記述することが望ましいです。
nameof演算子の利用
メソッド名を文字列で直接指定する代わりに、nameof
演算子を使うと、リファクタリング時に名前の変更が検出されやすくなります。
var methodName = nameof(MyClass.MyMethod);
var method = typeof(MyClass).GetMethod(methodName);
これにより、メソッド名の変更漏れによるMissingMethodException
を防げます。
Expression<>で型安全に
ラムダ式を使ってメソッド情報を取得する方法もあります。
using System;
using System.Linq.Expressions;
using System.Reflection;
public static MethodInfo GetMethodInfo<T>(Expression<Action<T>> expression)
{
var methodCall = (MethodCallExpression)expression.Body;
return methodCall.Method;
}
// 使用例
var methodInfo = GetMethodInfo<MyClass>(x => x.MyMethod(0));
この方法はコンパイル時に型チェックされるため、誤ったメソッド名やシグネチャの指定を防げます。
CI/CDで依存管理を自動化
依存関係のバージョン管理を自動化し、MissingMethodException
の発生を未然に防ぐことができます。
Dependabotとパイプライン連携
GitHubのDependabotなどのツールを使い、依存パッケージの更新を自動で検出・提案します。
CI/CDパイプラインで自動テストを実行し、更新による問題を早期に発見可能です。
- Dependabotがプルリクエストを作成
- CIでビルドとテストを自動実行
- 問題があれば通知やプルリクエストの拒否
これにより、依存関係の不整合や破壊的変更によるMissingMethodException
を防ぎ、安定した開発運用が実現します。
予防策
インターフェース中心の設計
インターフェースを中心に設計することで、実装の変更が呼び出し元に与える影響を最小限に抑えられます。
インターフェースは契約(APIの仕様)を明確に定義し、実装の詳細を隠蔽するため、メソッドの追加や内部ロジックの変更があっても、呼び出し元はインターフェースを通じて安定したアクセスが可能です。
例えば、以下のようにインターフェースを定義します。
public interface ILogger
{
void Log(string message);
}
実装クラスはこのインターフェースを実装し、内部の変更は自由に行えます。
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
呼び出し元はILogger
型で参照するため、実装の変更や拡張があってもMissingMethodException
のリスクを減らせます。
インターフェース中心の設計は、APIの後方互換性を保ちやすく、メソッドの削除やシグネチャ変更による例外発生を予防する効果があります。
API互換性チェックツールの導入
APIの変更がMissingMethodException
を引き起こさないように、API互換性を自動的にチェックするツールを導入することが効果的です。
これにより、破壊的な変更を事前に検出し、問題の発生を未然に防げます。
Microsoft ApiCompatの活用
Microsoftが提供するApiCompat
は、2つのアセンブリ間のAPI互換性を比較するツールです。
主にライブラリのバージョンアップ時に、公開APIの破壊的変更を検出するために使われます。
使い方の概要は以下の通りです。
- ApiCompatのインストール
NuGetパッケージとして提供されているため、CI環境やローカルに導入します。
- 比較対象の指定
旧バージョンのアセンブリと新バージョンのアセンブリを指定して比較します。
- レポートの生成
破壊的な変更(メソッドの削除、シグネチャ変更、アクセス修飾子の変更など)が検出されると、詳細なレポートが出力されます。
- CIパイプラインへの組み込み
ビルドやリリースの自動化パイプラインに組み込み、API互換性チェックを自動化します。
これにより、MissingMethodException
の原因となるAPIの破壊的変更を早期に発見し、修正や対応を行えます。
シグネチャ変更時の通知フロー
APIのシグネチャを変更する際は、チーム内での情報共有とレビュー体制を整えることが重要です。
通知フローを明確にし、変更の影響範囲を把握してから実施することで、MissingMethodException
の発生を防げます。
Pull Requestテンプレート
Pull Request(PR)テンプレートにAPI変更に関するチェックリストや通知項目を追加し、変更時の注意を促します。
例えば、以下のようなテンプレートを用意します。
# API変更チェックリスト
- [ ] メソッドのシグネチャ変更を含みますか?
- [ ] 変更内容をチームに通知しましたか?
- [ ] 互換性テストを実行しましたか?
- [ ] ドキュメントを更新しましたか?
- [ ] Obsolete属性の付与やラッパーメソッドの検討をしましたか?
# 変更の詳細説明
(ここに変更内容と影響範囲を記述してください)
このテンプレートを使うことで、APIのシグネチャ変更が含まれるPRは必ずレビューやテストが行われ、チーム全体で認識を共有できます。
結果として、MissingMethodException
の発生リスクを低減できます。
実例シナリオ
アプリ起動時のクラッシュ例
あるC#アプリケーションで、起動直後にMissingMethodException
が発生しクラッシュするケースがあります。
原因は、参照している外部ライブラリのバージョン不整合です。
例えば、以下のような状況です。
- アプリケーションは
ExampleLib
バージョン2.0.0を参照してビルドされています - 実行環境には古いバージョン1.5.0の
ExampleLib.dll
が配置されています - バージョン2.0.0で追加されたメソッド
NewFeature()
を起動時に呼び出しています
この場合、実行時に古いDLLが読み込まれ、NewFeature()
が存在しないためMissingMethodException
が発生します。
public class Program
{
public static void Main()
{
var lib = new ExampleLib.FeatureClass();
lib.NewFeature(); // バージョン1.5.0には存在しないメソッド
}
}
System.MissingMethodException: メソッド 'ExampleLib.FeatureClass.NewFeature()' が見つかりません。
この問題は、ビルド後に不要な古いDLLが実行フォルダに残っていることが多いです。
解決策としては、DLLのバージョンを統一し、ビルド出力をクリーンに保つことが重要です。
ASP.NET CoreでのHot Reload後問題
ASP.NET CoreのHot Reload機能を使って開発中にコードを修正し、アプリケーションを再起動せずに変更を反映させる際に、MissingMethodException
が発生することがあります。
これは、Hot ReloadがDLLの差分を動的に適用する過程で、メソッドのシグネチャが変更された場合に、古いメソッド呼び出しが残ってしまうためです。
例えば、以下のようにメソッドのパラメータを追加した場合、
// 変更前
public void ProcessData(int id) { /* 処理 */ }
// 変更後
public void ProcessData(int id, string option = "default") { /* 処理 */ }
Hot Reload適用後に、古い呼び出しコードがまだProcessData(int)
を呼んでいると、実行時にMissingMethodException
が発生します。
この問題を回避するには、Hot Reload適用後にアプリケーションを完全に再起動するか、呼び出し元コードも再コンパイルして最新のシグネチャに合わせる必要があります。
WPFバインディングでの例外
WPFアプリケーションで、XAMLのバインディング先のViewModelに存在しないメソッドやプロパティを指定すると、実行時にMissingMethodException
が発生することがあります。
例えば、以下のようなXAMLコードがあるとします。
<Button Content="Click Me" Command="{Binding NonExistentCommand}" />
ViewModelにNonExistentCommand
プロパティが存在しない場合、バインディング時に例外が発生します。
public class MainViewModel
{
// NonExistentCommand プロパティが存在しない
}
この例外は、バインディングエラーとしてVisual Studioの出力ウィンドウに表示されることもありますが、場合によってはMissingMethodException
としてアプリケーションがクラッシュすることもあります。
対策としては、
- バインディング先のプロパティやメソッドが正しく存在するか確認します
- バインディングエラーを検出するために
PresentationTraceSources.TraceLevel
を設定し、デバッグログを活用します - ViewModelの変更時にXAMLのバインディングも更新します
などが挙げられます。
これらの実例は、MissingMethodException
がどのような状況で発生しやすいかを示しており、原因の特定と対策に役立ちます。
MissingFieldExceptionとの違い
MissingMethodException
とMissingFieldException
は名前が似ていますが、対象となるメンバーが異なります。
- MissingMethodException
実行時に呼び出そうとしたメソッドが存在しない場合にスローされます。
例えば、メソッドのシグネチャが変更されたり、メソッド自体が削除された場合に発生します。
- MissingFieldException
実行時にアクセスしようとしたフィールド(変数や定数)が存在しない場合にスローされます。
例えば、クラスのフィールドが削除されたり、名前が変更された場合に発生します。
どちらもSystem.MissingMemberException
の派生クラスであり、メンバーが見つからないことを示しますが、MissingMethodException
はメソッドに、MissingFieldException
はフィールドに特化しています。
EntryPointNotFoundExceptionとの違い
EntryPointNotFoundException
は、主にP/Invoke(プラットフォーム呼び出し)でネイティブDLLのエントリポイント(関数)が見つからない場合にスローされます。
- EntryPointNotFoundException
ネイティブDLLの関数名が間違っている、またはDLL自体が存在しない場合に発生します。
例えば、[DllImport]
で指定した関数がDLLに存在しないときに起こります。
- MissingMethodException
.NETマネージドコード内でメソッドが見つからない場合に発生します。
主にアセンブリのバージョン不整合やメソッドシグネチャの不一致が原因です。
つまり、EntryPointNotFoundException
はネイティブコードの関数呼び出しに関する例外であり、MissingMethodException
はマネージドコードのメソッド呼び出しに関する例外です。
NullReferenceExceptionとの違い
NullReferenceException
は、null参照に対してメソッドやプロパティを呼び出そうとした場合に発生します。
- NullReferenceException
例えば、オブジェクトがnull
の状態でobj.Method()
を呼び出すと発生します。
これは「オブジェクトが存在しないため、メソッドを呼べない」という意味です。
- MissingMethodException
オブジェクト自体は存在しているが、呼び出そうとしたメソッドが存在しない場合に発生します。
例えば、ライブラリのバージョン違いやメソッドの削除によって、実行時にメソッドが見つからないケースです。
まとめると、NullReferenceException
は「オブジェクトがnullであることによる呼び出し失敗」、MissingMethodException
は「オブジェクトは存在するがメソッドが存在しないことによる呼び出し失敗」という違いがあります。
まとめ
MissingMethodException
は、実行時に呼び出そうとしたメソッドが存在しない場合に発生する例外です。
主な原因はライブラリのバージョン不整合やメソッドシグネチャの変更、アクセス修飾子の変更などです。
例外メッセージやスタックトレース、Fusionログを活用して原因を特定し、DLLのバージョン固定や全プロジェクトの再ビルド、後方互換性を保つ実装などで解決できます。
インターフェース設計やAPI互換性チェックの導入で予防し、CI環境での自動検出も効果的です。