WinFormsアプリでToast通知を扱うためにMicrosoft.Toolkit.Uwp.Notificationsを導入したところ、起動直後にSystem.TypeLoadExceptionが発生して落ちるケースがあります。多くはNotificationActivatorが実行時に見つからない「参照・バージョン不整合」が原因です。この記事では原因の見分け方と、最短で直す手順を具体的に整理します。
症状:WinForms実行時に「NotificationActivator 型を読み込めない」
今回のトラブルで典型的なのは、Windows Formsアプリを起動した瞬間(もしくは通知関連の初期化を行ったタイミング)でアプリが落ち、次のような例外が表示されるパターンです。
System.TypeLoadException
「Microsoft.Toolkit.Uwp.Notifications」アセンブリから
「Microsoft.Toolkit.Uwp.Notifications.NotificationActivator」型を読み込めません。
メッセージにNotificationActivatorが出ている場合、ほとんどは「実行時に読み込まれているDLLの中に、その型が存在しない(または依存関係が欠けていて読み込めない)」ことが直接原因です。つまり、コードが前提にしている状態と、実行環境で解決された状態がズレています。
結論:まずは参照とバージョンを揃えるのが最短ルート
この手のTypeLoadExceptionは、原因の切り分けを丁寧にやるほど深みにハマりがちです。まずは次の2点を徹底すると、かなりの確率で解消します。
- NuGet参照が正しいか(プロジェクトが必要なパッケージを参照しているか/参照が壊れていないか)
- バージョンが一致しているか(ビルド時に参照した版と、実行時に読み込まれた版が一致しているか)
以降では「なぜそう言えるのか(仕組み)」と「実務で効く確認手順」を、WinForms開発の現場目線で詳しくまとめます。
なぜ起きるのか:TypeLoadExceptionは「読み込んだDLLに型がない」サイン
System.TypeLoadExceptionは、ざっくり言うとCLR(.NETの実行基盤)が型の読み込みに失敗したときに発生します。今回のエラー文は特に分かりやすく、次のどちらか(または両方)が起きています。
| 起きていること | 具体例 | 結果 |
|---|---|---|
| 読み込まれたアセンブリに型が存在しない | 実行時に古いMicrosoft.Toolkit.Uwp.Notifications.dllが読み込まれ、NotificationActivatorが未収録 | TypeLoadExceptionで即クラッシュ |
| 型はあるが、依存DLL不足などで型のロードに失敗する | NotificationActivatorの内部で参照する別DLLが不足していて、型の解決が途中で失敗 | 「型が読み込めない」扱いになりTypeLoadExceptionになることがある |
| 別の場所のDLLが優先的に解決される | アプリの配置フォルダやGACなどに別バージョンがあり、そちらが先に見つかる | 参照しているつもりの版と違うものがロードされる |
重要なのは、「コードが間違っている」より「ビルドと実行の前提がズレている」可能性が高いという点です。特に通知系(UWP系APIと親和性が高いライブラリ)は、依存関係が増えやすく、複数プロジェクト・複数パッケージが絡むとズレが起きやすくなります。
最初にやるべき確認:実行時にどのDLLが読み込まれているか
「バージョン不一致」と言われても、どこがズレているか分からないと直せません。まずは実行時に実際にロードされたMicrosoft.Toolkit.Uwp.Notifications.dllの場所とバージョンを特定します。
Visual Studioで確認する(最も確実)
- デバッグ実行(F5)で例外が出るところまで進める
- メニューのデバッグ → ウィンドウ → モジュールを開く
- 一覧から
Microsoft.Toolkit.Uwp.Notifications.dllを探し、パスとバージョンを見る
ここで表示されるパスが、想定外の場所(古いインストールフォルダ、別アプリのディレクトリ、共有領域など)になっていたら、それがほぼ原因です。
コードで確認する(ログに残したい場合)
例外が出る直前に次のようなログを仕込むと、ユーザー環境でも調査しやすくなります。
using System;
using Microsoft.Toolkit.Uwp.Notifications;
public static class Diagnostics
{
public static void DumpToolkitAssemblyInfo()
{
var asm = typeof(ToastContentBuilder).Assembly;
Console.WriteLine(asm.FullName); // アセンブリ名 + AssemblyVersion
Console.WriteLine(asm.Location); // 読み込み元のフルパス
}
}
出力されるFullName(アセンブリ名+バージョン)とLocation(実体パス)が一致しているかがポイントです。
補足:アセンブリバージョンとファイルバージョンは別物
バージョン確認で混乱しやすいのが、AssemblyVersionとFileVersionが一致しないケースです。Windowsのファイルプロパティで見える「ファイル バージョン」は、必ずしも.NETのバインディング(読み込み解決)で使われる値ではありません。
| 種類 | どこで見る | 主な用途 | 今回のトラブルとの関係 |
|---|---|---|---|
| AssemblyVersion | asm.FullName、参照情報、バインディング | .NETがアセンブリを同一性判定する | 最重要(ズレると別物扱いになりやすい) |
| FileVersion | DLLのプロパティ、VersionInfo | ファイルの表示用(サポート情報など) | 参考にはなるが、これだけ見て判断すると危険 |
「プロパティ上は新しいのに落ちる」場合、AssemblyVersionのズレや、別場所のDLLロードが隠れていることがあります。前述のasm.FullNameで出る値を優先してください。
原因の代表例:なぜ「違うDLL」が読み込まれてしまうのか
NotificationActivatorが読み込めないときの「あるある原因」を、現場で遭遇しやすい順に並べます。まずはここから当てはまるものを探すと早いです。
| 原因 | よくある状況 | 見分け方 | 対処 |
|---|---|---|---|
| NuGetの参照が壊れている/復元できていない | パッケージは入っているが、ビルド出力にDLLが出てこない | 出力フォルダにDLLが無い、または参照警告が出る | 再インストール、復元(restore)、クリーンビルド |
| バージョン不一致(解決されたDLLが古い) | 複数プロジェクトでパッケージ版が混在している | モジュールウィンドウで読み込み版が想定より古い | Consolidateで統一、明示的に同一バージョンへ |
| 出力フォルダに古いDLLが残っている | 以前のビルド成果物が残り、差し替わっていない | bin/obj削除で症状が変わる | bin/obj削除、インストール先フォルダの掃除 |
| GACや共有領域に別版があり、そちらが拾われる | MSIで配布する製品・別製品が共有DLLを入れている | 読み込みパスがGACや想定外の場所 | 共有配置を避ける、アンインストール、バインド調査 |
| 他パッケージが依存関係として別版を要求している | 通知以外のパッケージが同名DLLの別版を引っ張る | トランジティブ依存を調べると別版が混ざる | 依存関係を整理し、最終的に1つへ統一 |
解決策:NuGet参照を確認し、バージョン不一致を解消する
ここからが本題です。TypeLoadExceptionは「必要な型が見つからない」エラーなので、必要な型を含む正しいDLLを、実行時に確実に読み込ませるのがゴールです。手順を細かく書きます。
プロジェクトにパッケージが入っているかを確認する
Visual Studioの場合、次のどちらかで確認します。
- ソリューションエクスプローラー → 対象プロジェクト → 依存関係(または参照) → NuGet一覧
- プロジェクトを右クリック → NuGetパッケージの管理 → インストール済み
Microsoft.Toolkit.Uwp.Notificationsが「このプロジェクト」に入っているか、そして警告マークが付いていないかを見ます。通知関連の処理を別クラスライブラリに書いている場合は、実装があるプロジェクト側の参照も必ず確認してください。
ソリューション内でバージョンを統一する(混在は危険)
複数プロジェクト構成(例:WinForms本体+共通ライブラリ+別ツール)だと、同じパッケージが別バージョンで入りやすくなります。Visual Studioの「Consolidate(統合)」画面で、ソリューション内のパッケージ版を揃えるのが効果的です。
- NuGetパッケージの管理(ソリューション)を開く
- 統合(Consolidate)タブで、同一パッケージの複数バージョンが出ていないか確認
- 混在していたら1つのバージョンに揃える
「コード側が前提にしているバージョン(例:7.1.0.0)」と「実行時にロードされるDLL」が一致するのが理想です。どちらが正解か迷う場合は、まず最新に合わせるよりもソリューション全体で1つに固定することを優先してください。混在状態のほうが事故率が高いです。
更新・再インストールで参照のズレを解消する
NuGetの復元状態が崩れていると、見た目は入っているのにビルド結果に反映されないことがあります。次の手順が効きやすいです。
- 対象パッケージを一度アンインストールする
- ソリューションを保存し、Visual Studioを再起動する
- 同じバージョン(または統一したいバージョン)で再インストールする
- ソリューション全体で「復元(Restore)」を実行する
「更新だけ」より「一度外して入れ直す」ほうが、参照メタデータの破損を巻き込んで直るケースが多い印象です。
コマンドで依存関係を棚卸しする(特に.NET 6/7/8系)
SDKスタイルのプロジェクトなら、トランジティブ依存(間接依存)まで含めて確認できます。どこかが古い版を引っ張っている場合に見つけやすいです。
dotnet list YourProject.csproj package --include-transitive
出力に同じパッケージ名が複数バージョンで出ていないか、意図せぬ依存(通知とは無関係に見えるパッケージ)が絡んでいないかを確認します。
解決策:出力フォルダの古いDLLを一掃してから再ビルドする
参照を直しても直らないとき、次に疑うべきはビルド成果物の残骸です。特にWinFormsは「動いていた過去のDLL」がbinフォルダに残りやすく、アップデートで差し替わっていないと事故ります。
基本:bin/objを削除してクリーンビルド
- Visual Studioを終了する
- ソリューション配下の各プロジェクトの
binとobjを削除する - 再度ビルドする
「クリーン」だけでは残るものがあるため、手動で削除するのが確実です。
PowerShellでDLLの重複を探す(意外と効く)
ローカルの作業フォルダやインストール先に、同名DLLが複数存在すると混乱が起きます。次のように探すと、どこに何があるか見える化できます。
Get-ChildItem -Path "C:\\YourSolutionRoot" -Recurse -Filter "Microsoft.Toolkit.Uwp.Notifications.dll" |
Select-Object FullName,
@{Name="FileVersion";Expression={(Get-Item $_.FullName).VersionInfo.FileVersion}},
@{Name="ProductVersion";Expression={(Get-Item $_.FullName).VersionInfo.ProductVersion}}
同じ名前のDLLが複数見つかり、バージョンがバラバラなら要注意です。配布物(インストーラー)にも同梱される場所を確認し、最終的に1つの正しいDLLだけが配置される状態を目指します。
(.NET Framework向け)bindingRedirectの確認ポイント
ターゲットフレームワークが.NET Frameworkの場合、実行時のアセンブリ解決にapp.configのbindingRedirectが関わることがあります。ここが古いままだと、「参照は最新にしたのに、実行時は古い版へリダイレクトされる」現象が起きます。
典型例として、次のような構成が挙げられます。
- WinForms(.NET Framework 4.7.2/4.8など)
- 複数のNuGetパッケージが同じ依存DLLを要求
- 自動生成されたbindingRedirectが古い/更新されない
実際の記述はプロジェクトによって異なりますが、考え方はシンプルで、最終的に読み込ませたいバージョンへ統一することです。設定を自動生成に任せる場合でも、次の点は確認してください。
| 確認項目 | 見る場所 | ポイント |
|---|---|---|
| bindingRedirectが存在するか | app.config / YourApp.exe.config | 同名アセンブリのredirectが複数・矛盾していないか |
| oldVersion/newVersionの範囲 | assemblyBinding | 意図せず古い版へ固定されていないか |
| 自動生成の設定 | プロジェクトのプロパティ | 自動生成ONでも実際に更新されるとは限らない |
もし設定に自信がない場合は、まずは参照の統一(NuGetのConsolidate)を先にやってからconfigを見直すと、設定の複雑さが減って作業しやすくなります。
「UWP用ライブラリをWinFormsで使ってよいの?」への答え
結論から言うと、通知(Toast)周りはWinFormsのデスクトップアプリからでも利用できます。ただし、WinFormsそのものがUWPになるわけではなく、あくまで「デスクトップアプリがWindowsの通知機構を使う」という位置付けです。そのため、次のような落とし穴が出やすくなります。
- 依存関係が増え、実行時のDLL解決でズレが表面化しやすい
- 通知のクリック(アクティベーション)までやる場合、COM登録やActivatorクラスが絡み、初期化が複雑になる
- 開発PCでは動くのに、配布先(MSI/ClickOnce/Zip配布)で落ちる、といった配布形態依存が起きる
つまり「使えるが、参照と配布を丁寧に扱う必要がある」というのが現実的な答えです。
NotificationActivatorが絡む実装の要点(必要なケース/不要なケース)
NotificationActivatorは「通知を表示する」だけなら必須ではないケースもあります。一方で「通知をクリックしたときにアプリで何か処理したい」場合は重要になります。自分の要件を切り分けると、設定の複雑さを下げられます。
| やりたいこと | NotificationActivatorの重要度 | コメント |
|---|---|---|
| とにかくToast通知を表示したい | 低い(構成次第) | 表示だけならBuilder系で完結することが多い |
| 通知をクリックしたら画面を開きたい | 高い | アクティベーション処理の登録が必要になりやすい |
| 通知のボタン入力やテキスト入力を受け取りたい | 高い | ユーザー入力(UserInput)を扱うため実装が増える |
もし現時点で「クリック時の処理が不要」なら、まずは表示だけの最小構成で動作確認し、そこから段階的に機能を足すと原因の切り分けが楽になります。
配布形態別の注意点:開発PCで動くのに配布先で落ちるとき
このエラーが厄介なのは、開発環境のbinフォルダでは動くのに、配布後の実行環境でだけ落ちるケースがある点です。配布の種類ごとに「古いDLLが残る/混ざる」ポイントが違います。
| 配布形態 | 起きやすい問題 | 確認ポイント |
|---|---|---|
| MSI | アップデート時に古いDLLが残り、想定外の版がロードされる | インストール先フォルダに同名DLLが複数残っていないか、上書き/削除ルールは正しいか |
| ClickOnce | キャッシュに古い版が残り続ける/参照が壊れた状態で復元される | クライアント側のキャッシュをクリアした場合に再現するか |
| Zip配布(手動コピー) | ユーザーが上書きコピーに失敗し、古いDLLが残る | 「全ファイル置き換え」になっているか、差分コピーで漏れていないか |
いずれの配布形態でも、「実行フォルダに置かれたDLL」が最終的に勝ちます。配布先で落ちる場合は、まずインストール先のMicrosoft.Toolkit.Uwp.Notifications.dllを確認してください。
それでも直らないときの切り分け:環境依存の競合を疑う
質問コメントなどで「MSIで入れたソフトを片っ端からアンインストールしたら直った」という報告が出ることがあります。これは少し極端ですが、考え方としては次のどれかが起きています。
- 別製品が同名DLLを共有領域(例:GAC)に入れており、そちらが優先されている
- インストール先フォルダに古いDLLが残り、アップデートで差し替わっていない
- 配布パッケージの中に古いDLLが混ざっており、常に古い版が展開される
ただし、闇雲なアンインストールは再現性が低く、原因も追えなくなるためおすすめしません。代わりに、次の順番で安全に切り分けるとよいです。
- モジュールウィンドウ/ログで「読み込まれたDLLのパス」を特定する
- そのパスにあるDLLのバージョン(できればAssemblyVersion)を確認する
- なぜそこにDLLが存在するのか(インストーラーが置いたのか、手動コピーか、別アプリ由来か)を追う
- 原因箇所を1つずつ潰す(同梱の修正、古いDLLの削除、共有配置の撤廃など)
これなら「直ったけど理由が分からない」を避けられ、同じトラブルの再発も防げます。
再発防止:バージョン不一致を起こさない運用のコツ
一度直しても、別のメンバーがパッケージ更新した瞬間に再発することがあります。通知ライブラリは周辺依存も多いので、次の運用が効きます。
- ソリューション内でパッケージバージョンを統一する(混在を許さない)
- 更新する場合は1プロジェクトだけ更新しない(関連プロジェクトも同時に)
- 配布前に出力フォルダのDLL一覧とバージョンをチェックする(自動テストに組み込むのも有効)
- インストーラーを使うなら、古いDLLが残らないようにアップデート時の上書き・削除ルールを明確化する
開発環境では直っているのに配布先で落ちる場合、インストールフォルダに古いDLLが残っているケースが多いです。配布手順(MSI/ClickOnce/Zip)ごとに「古いファイルを確実に掃除できるか」を一度見直すだけで、将来の工数が大きく減ります。
最後に:最小チェックリスト(同じ例外を見たらここを見る)
本記事の要点を、作業の順番にチェックリスト化します。迷ったら上から順に潰してください。
- プロジェクトで
Microsoft.Toolkit.Uwp.Notificationsを正しく参照している - ソリューション内で同一パッケージのバージョンが混在していない
- 実行時に読み込まれたDLLのパスとバージョンを確認した
- bin/objを削除してクリーンビルドした
- (.NET Frameworkの場合)app.configのbindingRedirectが矛盾していない
- 配布物(MSIなど)のインストール先に古いDLLが残っていない
TypeLoadExceptionは「必ずどこかでズレている」という性質のエラーです。読み込まれているDLLを特定し、参照と出力を揃える。この2点を軸に調査すれば、NotificationActivatorが読み込めない問題は高い確率で解消できます。

コメント