連載Index(読む順・公開済(リンク)はここが最新): S00_門前の誓い_総合Index
依存事故が一番タチ悪いのは、「配置先に古いDLLが残っている」時だ。
コードは正しいし、NuGetの復元も通る。なのに起動しない。
その瞬間、犯人はソースではなく“混在”になる。
新旧が混ざった実行フォルダは、都合よく壊れる。
このページは、NuGet競合(復元時)だけでなく、
配置先に残ったDLL(実行時)まで含めて、止血と再発防止をセットでまとめる。
理由は、“同じはず”を破る要素が端末側に積まれているからだ。
-
nuget.configの差(参照しているフィード/sourceや優先順位が違う) - 認証/プロキシの差(社内フィードに繋がらず、取得経路が変わる)
- キャッシュ/復元の残骸(古いパッケージや古い解決結果が残る)
- SDK/VS/MSBuildの差(復元・ビルドの挙動や警告/エラー扱いが変わる)
- 配布残骸(実行時だけ:古いdllが残って“新旧混在”になる)
……またお前か。依存関係のバージョン違いだ。
コードじゃない。コードじゃないんだ。。
まず 「どこで死んでるか」 と 「何が選ばれたか」 を見ろ。
症状→原因→止血→恒久対策→再発防止を、迷子にならないチェック順でまとめます。
最短3分ルート(初心者はここだけやれ)
-
ビルド出力/CIログで
NUxxxxを探す(依存で死んでるかの当たり判定) -
dotnet restore -v minimal(.sln / .csproj の場所で) -
dotnet list <csproj> package --include-transitive(解決Version=確定情報) -
NU1107/NU1605が出たら「上位でVersion宣言」して揃える(止血) - 直らなければ
dotnet clean→dotnet nuget locals all --clear→ restore/build
0. まずそれ、依存で死んでる?(当たり判定)
結論:依存事故かどうかは “エラーの出どころ” で判定する。
最初に「いま見えているエラーが、どこに出ているか」を決める。ここを外すと永遠に迷う。
-
CI/ローカルのビルド出力に出ている →
dotnet restore/buildの出力を見ろ - アプリが起動して落ちる(実行時) → 例外ログ(アプリログ/イベントログ)を見ろ
- 「端末によって結果が違う」 → “同じはずが同じになってない”前提で、解決結果と残骸を疑え
補足(Visual Studio)
-
NUxxxx/CSxxxxは、まず「エラー一覧」か「出力ウィンドウ(ビルド)」に出る - “どこで死んでるか”は、エラー一覧の分類(復元/ビルド/実行)でだいたい分かる
0-1. 端末差が出る因果(先に1枚で腹落ち)
0-2. 依存で死んでるサイン(どこに出るか込み)
| どこを見てる? | サイン(例) | だいたい犯人 | まずやる |
|---|---|---|---|
| CI/ローカルのビルド出力(VSの出力/ターミナル) |
NU1107 NU1605 NU1301 など NUxxxx
|
NuGet(競合/ダウングレード/取得失敗) | dotnet restore -v minimal |
| CI/ローカルのビルド出力(VSの出力/ターミナル) |
CS1705(参照アセンブリのVersion差) |
参照バージョン不一致 | dotnet list package --include-transitive |
| 実行時ログ(アプリログ/Windowsイベントログ/ダンプ) |
Could not load file or assembly... / FileLoadException / MissingMethodException
|
“古い/別のdll”をロード | ロードされたdllの Location/Version を取る |
| 現象(端末差/環境差) | 「Aは動くのにBだけ死ぬ」 | 解決結果の差 / 配布残骸 / source差 | 解決結果 と 残骸 を疑う |
補足:
- 「どこに出るか」が分からないなら、まずは ビルド出力(VSの出力 or ターミナル) に寄せる。そこが一番早い。
- 本番で実行時に落ちるなら、まず アプリのログ。無ければ Windowsイベントログ(Application) を見ろ。
0-3. restore単体で当たりを出す(まず叫ばせろ)
失敗しているプロジェクト(.sln か .csproj のある場所)で叩く。
依存が原因なら、ビルドの前に restore が叫ぶことが多い。
dotnet restore -v minimal
- ここで NUxxxx が出たら → 依存(NuGet)で死んでる。次へ。
- ここが通って、ビルドで
.csの行を指して落ちる → まずコード。 - restore は通るのに実行時だけ落ちる → 配布残骸/ロード差の可能性が濃い(後半へ)。
0-4. 解決されたバージョンを見る(ここが本丸)
「何を見るか」が曖昧だと迷子になる。見る対象を固定する。
dotnet list <失敗しているcsproj> package --include-transitive
ここで見るのは2点だけ。
- 同じパッケージが複数バージョン要求されてないか
- 最終的にどのバージョンが選ばれたか(=解決されたバージョン)
1. 用語を先に潰す(中央管理/競合/ロック/確定情報)
1-1. Directory.Packages.props って何?
一言で言うと 「ソリューション全体の NuGet パッケージVersion表」。
.csproj に散らばった Version 指定を、1枚のファイルに集めて “全員を同じVersionにする” ために使う。
- これは NuGet の仕組み(MSBuild + NuGet の復元処理が読む)
- VS専用ではない(CLI
dotnet restoreでも効く) - 目的は「端末差」「プロジェクト差」で Version が揺れて壊れるのを止めること
1-2. 「競合しているパッケージ」ってどういう状態?
同じソリューション内で、同じパッケージに対して
- ある依存は
A >= 1.0を要求 - 別の依存は
A >= 2.0を要求
みたいに 要求Versionが割れている 状態。
この割れが表に出ると NU1107(競合)や NU1605(ダウングレード)が出る。
重要:あなたが直接入れたパッケージじゃなくても起きる(推移依存が揉める)。
1-3. ロック(packages.lock.json)って何?
一言で言うと 「今回復元で選ばれたパッケージVersion一式を固定する台帳」。
次回から、その台帳と違う復元結果になるのを拒否できる。
- “鍵”ではなく 固定(ロック) の意味
- 目的は「昨日と今日で、復元結果が勝手に変わる」を止めること
1-4. このページで言う「確定情報」って何?
推測で殴ると時間が溶ける。
だから “いまこの端末で実際に選ばれたVersion/実体” を後で追える形で残す。
-
dotnet list package --include-transitiveの結果(解決Version一覧) -
obj/project.assets.json(解決結果の判決文(決文)) - 実行時にロードされたdllの
Location/Version(現物)
2. このページで使うコマンド(何をして、どこに出る?)
前提:ここで使うのは dotnet コマンド。
内部で NuGet(復元) や MSBuild(ビルド) を呼び出している。
2-1. dotnet restore(復元)
何をする?
- NuGetパッケージを取得し、依存関係を解決して、参照できる状態にする。
どこに出る?
- コンソール/CIログに
NUxxxxが出る -
obj/project.assets.jsonが生成される(解決結果)
よく使う:
dotnet restore -v minimal
2-2. dotnet list package(解決結果の一覧)
何をする?
- そのプロジェクトで「最終的に使われるパッケージVersion」を一覧する。
-
--include-transitiveで「依存の依存」まで出す(揉めてるのはだいたいこっち)。
dotnet list <csproj> package --include-transitive
2-3. dotnet build(ビルド)
何をする?
- コンパイルとリンク。ここで
CS1705など “参照Version差” が表に出ることがある。
2-4. dotnet clean / dotnet nuget locals(掃除)
何をする?
- 残骸を消す。参照事故は残骸で再現が消えるので、途中で入れる価値がある。
dotnet clean
dotnet nuget locals all --clear
3. 困ったらここから(最短ルート)
-
復元(restore)で死ぬ →
NUxxxxを見ろ(まずdotnet restore) -
ビルド(build)で死ぬ →
CS1705等 “参照Version差” を見ろ -
実行(run)で死ぬ →
FileLoadException / MissingMethodExceptionを“ロード元(パス)”で殺せ
4. 症状(まず分類)
| どこで死ぬ | 典型メッセージ | まず取る確定情報 |
|---|---|---|
| restore(復元) |
NU1107 NU1605 NU1301
|
restoreログ / project.assets.json
|
| build(コンパイル) | CS1705 |
失敗プロジェクト / dotnet list package
|
| run(実行時) |
Could not load file or assembly... 等 |
ロードdllの Location/Version |
| 端末差が出る | 「同じ成果物のはずなのに…」 | 解決結果の差分 / 配布残骸 |
5. 原因(結論)
依存事故の主犯は、だいたいこの4つ。
| 主犯 | 何が起きる |
|---|---|
| 参照方式の混在 | 同名dllが二重に入り、どれがロードされたか分からなくなる |
| バージョンの揺れ | 推移依存で別Versionが選ばれて端末差が出る |
| 配布残骸 | 古いdllが残って“新旧混在”で実行時に爆発 |
| ソース/キャッシュの汚れ | 取れるパッケージが環境で変わる |
まとめ(ここだけ持ち帰れ)
-
NUxxxxが出たら:まずdotnet restore -v minimal(依存で死んでる当たり判定) - 迷ったら:
dotnet list <csproj> package --include-transitive(解決されたVersion=確定情報) - 実行時だけ死ぬなら:ロード元(Location/Version)をログ→配布は「上書き禁止・丸ごと入替」