例外処理

【C#】MissingMethodExceptionの原因と解決策:ライブラリのバージョン差異からメソッドシグネチャ変更まで

MissingMethodExceptionは実行時に呼び出そうとしたメソッドのシグネチャが見つからないときに発生します。

ライブラリのバージョン差異、メソッド名や引数変更、アクセス修飾子の変更が主因です。

参照DLLをそろえ再ビルドし、古いアセンブリを削除すれば多くのケースで解消できます。

目次から探す
  1. MissingMethodExceptionとは
  2. 代表的な発生原因
  3. 例外メッセージの読み解き方
  4. 再現手順の特定
  5. デバッグアプローチ
  6. 解決策
  7. 予防策
  8. 実例シナリオ
  9. まとめ

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.configPackageReferenceでバージョンを明示的に固定し、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からinternalprivateに変更すると、外部アセンブリやクラスからのアクセスが制限されます。

呼び出し元がアクセスできないメソッドを呼ぼうとすると、MissingMethodExceptionが発生します。

例えば、ライブラリのメソッドをpublicからinternalに変更した場合、別プロジェクトからそのメソッドを呼び出すと例外が発生します。

アクセス修飾子の変更はAPIの互換性に大きく影響するため、慎重に行う必要があります。

条件付きコンパイル

#if で除外されたメソッド

#ifディレクティブで特定のメソッドがコンパイルから除外されている場合、実行時にそのメソッドが存在しないためMissingMethodExceptionが発生します。

例えば、以下のようなコードでDEBUGシンボルが定義されていないときにメソッドが存在しません。

#if DEBUG
public void DebugOnlyMethod() { Console.WriteLine("Debug mode"); }
#endif

呼び出し元がDebugOnlyMethodを呼んでいると、リリースビルドで例外が発生します。

リフレクション呼び出し

MethodInfo取得失敗のパターン

リフレクションでメソッドを取得する際に、メソッド名やパラメータの型を間違えるとMethodInfonullになり、呼び出し時に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

ビルド構成がAnyCPUx64x86で混在していると、異なるプラットフォーム用のDLLが読み込まれ、メソッドが見つからないことがあります。

特にネイティブコードと連携する場合に注意が必要です。

例えば、64ビット環境で32ビットDLLを読み込もうとすると、MissingMethodExceptionBadImageFormatExceptionが発生することがあります。

アセンブリのロード順序

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ログを有効にする手順は以下の通りです。

  1. レジストリエディタを開く

regeditを起動します。

  1. Fusionログの設定を追加する

以下のパスに移動します。

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Fusion もしFusionキーがなければ作成します。

  1. 以下のDWORD値を追加・設定する
  • EnableLog : 1(ログを有効化)
  • ForceLog : 1(強制的にログを記録)
  • LogFailures : 1(失敗したバインドもログに記録)
  • LogResourceBinds : 1(リソースバインドもログに記録)
  1. ログの出力先を指定する

LogPath(文字列値)を作成し、ログを保存したいフォルダのパスを指定します。

例:C:\FusionLogs\

  1. アプリケーションを再起動して問題を再現する
  2. ログファイルを確認する

指定したフォルダに.htm形式のログファイルが生成されます。

これをブラウザで開くと、どのアセンブリがどこから読み込まれたか、失敗した場合はその原因が詳細に記録されています。

Fusionログは.NET Framework専用の機能であり、.NET Coreや.NET 5以降では利用できません。

その場合は、AssemblyLoadContextのイベントを利用したり、dotnet-traceなどのツールを使って調査します。

Fusionログを活用することで、MissingMethodExceptionの原因となるアセンブリのバージョン不一致やロード失敗を特定しやすくなります。

再現手順の特定

最小構成プロジェクトの作成

MissingMethodExceptionの原因を特定するためには、問題を再現できる最小限のプロジェクトを作成することが非常に効果的です。

複雑なプロジェクトや多くの依存関係が絡んでいる場合、問題の切り分けが難しくなるため、まずは問題の本質に絞ったシンプルな環境を用意します。

具体的には、以下の手順で進めます。

  1. 新規プロジェクトの作成

Visual Studioやdotnet newコマンドで新しいコンソールアプリケーションやクラスライブラリを作成します。

  1. 問題のメソッド呼び出しを再現するコードを記述

問題が発生しているメソッドの呼び出しだけを含むコードを書きます。

例えば、特定のライブラリのメソッドを呼び出すだけの簡単なコードにします。

  1. 依存関係を最小限に絞る

問題のライブラリだけを参照し、他の不要なパッケージやプロジェクト参照は外します。

  1. ビルドと実行で例外が発生するか確認

例外が再現できれば、問題の切り分けが成功しています。

再現しない場合は、依存関係や設定を少しずつ追加しながら再現条件を探ります。

この最小構成プロジェクトを使うことで、問題の原因がライブラリのバージョン違いなのか、メソッドシグネチャの不一致なのか、あるいはビルド設定の問題なのかを効率的に調査できます。

バージョン固定とロールバック

MissingMethodExceptionはライブラリのバージョン差異が原因で発生することが多いため、依存しているパッケージやDLLのバージョンを固定し、問題が発生しない安定したバージョンにロールバックすることが有効です。

具体的な手順は以下の通りです。

  1. 現在のバージョンを確認

csprojファイルやpackages.configPackageReferenceの設定を確認し、参照しているパッケージのバージョンを把握します。

  1. バージョンを明示的に固定

バージョン範囲指定を外し、特定のバージョンに固定します。

例えば、<PackageReference Include="ExampleLib" Version="1.2.3" />のように記述します。

  1. ロールバックの検討

問題が発生し始めたバージョンより前の安定版に戻してビルド・実行し、例外が発生しないか確認します。

  1. 依存関係の整合性を保つ

複数のプロジェクトが異なるバージョンを参照している場合は、全体でバージョンを統一します。

  1. バージョン管理ツールの活用

Gitなどのバージョン管理システムで、問題が発生する前のコミットに戻して動作確認を行うことも有効です。

バージョン固定とロールバックにより、どのバージョンから問題が発生したのかを特定しやすくなり、原因の切り分けが進みます。

CI環境での再現確認

継続的インテグレーション(CI)環境でMissingMethodExceptionの再現を確認することは、問題の早期発見と安定したビルドの維持に役立ちます。

CI環境での再現確認のポイントは以下の通りです。

  1. CIビルドの設定を最新に保つ

依存パッケージのバージョンやビルド設定をCIのビルドスクリプトに明示的に記述し、ローカル環境と同じ条件でビルドできるようにします。

  1. 最小構成プロジェクトをCIに組み込む

問題の再現に成功した最小構成プロジェクトをCIのテストスイートに追加し、例外が発生するか自動で検証します。

  1. バージョン管理と連携

依存パッケージのバージョンを固定し、CIでのビルドが成功するかを常にチェックします。

バージョンアップ時には自動テストで例外の有無を確認します。

  1. ログの収集と通知設定

CIで例外が発生した場合に詳細なログを収集し、開発チームに通知する仕組みを整えます。

  1. ロールバックや修正の検証

問題の修正やバージョンダウンを行った際に、CIで再度ビルド・テストを実行し、問題が解消されたかを確認します。

CI環境での再現確認を行うことで、開発チーム全体で問題の発生を早期に把握でき、安定したリリースを維持しやすくなります。

デバッグアプローチ

Fusionログビューア

Fusionログは、.NET Frameworkでアセンブリのバインド(読み込み)に関する詳細な情報を記録する機能です。

MissingMethodExceptionの原因がアセンブリのバージョン不一致やロード失敗にある場合、Fusionログを解析することで問題の特定が容易になります。

Assembly Bind Failure Viewerの使い方

Assembly Bind Failure Viewer(通称:Fuslogvw.exe)は、Fusionログを視覚的に確認できるMicrosoft公式ツールです。

以下の手順で使用します。

  1. ツールの起動

Visual Studioの開発者コマンドプロンプトやWindowsのスタートメニューからFuslogvw.exeを起動します。

通常はC:\Program Files (x86)\Microsoft SDKs\Windows\<バージョン>\bin\NETFX <version> Tools\にあります。

  1. ログの設定

ツールのメニューから「Settings」を開き、「Log bind failures to disk」にチェックを入れます。

必要に応じて「Enable custom log path」を指定し、ログの保存先を設定します。

  1. 問題の再現

アプリケーションを実行し、MissingMethodExceptionが発生する状況を再現します。

  1. ログの確認

Fuslogvwに戻り、「Refresh」ボタンを押すと、最近のバインド失敗ログが一覧表示されます。

該当するログをダブルクリックすると詳細が表示されます。

  1. ログ内容の解析

ログには、どのアセンブリがどのパスから読み込まれたか、どのパスを探したか、失敗の原因が記録されています。

これにより、誤ったバージョンのDLLが読み込まれているか、ファイルが見つからないかなどを判断できます。

  1. ログの無効化

調査終了後は、Fusionログを無効化してパフォーマンスへの影響を防ぎます。

Fusionログビューアを使うことで、アセンブリのロード問題を視覚的に把握でき、MissingMethodExceptionの根本原因を特定しやすくなります。

ildasm・ILSpyでの検証

MissingMethodExceptionは、実行時に呼び出そうとしたメソッドが存在しないことが原因です。

メソッドが本当にアセンブリ内に存在するかどうかを確認するには、IL(中間言語)レベルでの検証が有効です。

ildasm(IL Disassembler)やILSpyは、.NETアセンブリのILコードを閲覧できるツールです。

ILレベルでのメソッド存在確認

  1. ildasmの使用方法
  • Visual Studioの開発者コマンドプロンプトでildasmを起動します
  • 問題のDLLやEXEファイルを開きます
  • ツリー形式で名前空間やクラス、メソッドが表示されるので、該当のメソッド名を探します
  • メソッドが存在しない場合は、呼び出し元のコードとアセンブリの不整合が疑われます
  1. ILSpyの使用方法
  • ILSpyはGUIベースのオープンソースツールで、より使いやすいです
  • DLLやEXEをドラッグ&ドロップで開き、クラスやメソッドを検索できます
  • メソッドのシグネチャや実装コードも閲覧可能です
  1. メソッドのシグネチャ確認
  • メソッド名だけでなく、引数の型や戻り値の型も確認します
  • 呼び出し元のコードと完全に一致しているかをチェックします
  1. バージョン違いの検出
  • 期待しているメソッドが存在しない場合、参照している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.MyClassMyMethod(int, string)が存在するかを確認します。

メソッドが存在しなければテストが失敗し、問題を早期に検知できます。

このような互換性テストをCIパイプラインに組み込むことで、APIの破壊的変更を防ぎ、MissingMethodExceptionの発生リスクを低減できます。

さらに、複数のメソッドやクラスに対して同様のテストを作成し、網羅的にチェックすることも可能です。

解決策

DLLバージョンを固定する

MissingMethodExceptionの多くは、異なるバージョンのDLLが混在することによって発生します。

バージョンを固定し、依存関係の整合性を保つことが重要です。

BindingRedirectの設定方法

.NET Frameworkでは、異なるバージョンのアセンブリを参照している場合にBindingRedirectを使ってバージョンの不整合を解消できます。

app.configweb.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

ビルド出力フォルダbinobjのファイルをすべて削除します。

これにより、古いDLLや中間ファイルが残らず、クリーンな状態になります。

  • Rebuild

Cleanの後に全プロジェクトをビルドし直します。

依存関係も含めて最新の状態でビルドされるため、DLLのバージョン不整合やメソッドの不一致を防げます。

Visual Studioでは「ビルド」メニューから「クリーン」と「リビルド」を選択できます。

コマンドラインではdotnet cleandotnet 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の破壊的変更を検出するために使われます。

使い方の概要は以下の通りです。

  1. ApiCompatのインストール

NuGetパッケージとして提供されているため、CI環境やローカルに導入します。

  1. 比較対象の指定

旧バージョンのアセンブリと新バージョンのアセンブリを指定して比較します。

  1. レポートの生成

破壊的な変更(メソッドの削除、シグネチャ変更、アクセス修飾子の変更など)が検出されると、詳細なレポートが出力されます。

  1. 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との違い

MissingMethodExceptionMissingFieldExceptionは名前が似ていますが、対象となるメンバーが異なります。

  • 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環境での自動検出も効果的です。

関連記事

Back to top button
目次へ