編譯
當我們按下「Build」時…
編譯器先把 .cs 程式碼轉成中間結果放在 obj 資料夾,在 obj 裡面會產生有
- 暫存 DLL
- 編譯中間檔
- 自動產生的程式碼(例如 Razor)
當確認所有東西都沒問題後,才會輸出最終結果到 bin 而裡面會有
- .dll(主要程式)
- .exe(如果是應用程式)
- 相依套件 DLL(NuGet)
- 設定檔
也就是說真正執行網站時用的是 bin,而編譯過程中產生一堆暫存在 obj

有時候我們會看到刪掉 bin / obj 的建議來解決版本問題,例如我們若遇到錯誤 ReflectionTypeLoadException,是因為舊的編譯垃圾如果還被拿來用,很可能 bin 裡殘留舊版 DLL、新版 NuGet 已經下載,但舊 DLL 還在、執行時載到錯的版本,而我們刪掉它,就會強制全部重新編譯 + 重新產生正確 DLL

首先,以編譯器本人的角度來說,他不會自己去「找檔案」,它只會編譯「專案明確告訴它要編譯的清單」
當我們啟動 Build,其實是啟動 MSBuild,而 MSBuild 會先讀 .csproj 檔,這個檔案是一份「建置腳本」(不是單純設定檔),而在 .csproj 裡有這些東西
1 | <ItemGroup> |
這代表一件事「只有這些檔案要被拿去編譯」,MSBuild 會把這些
舊 .NET Framework 的設計理念是完全掌控編譯內容,只是其中代價就是容易漏檔案,因為專案要編譯什麼必須明確寫出來

.NET CORE
現代專案改成「預設全抓」,因為少犯錯比精細控制更重要,SDK-style .csproj 其實內建一條規則
1 | <Compile Include="**\*.cs" /> |
意思是整個專案底下所有 .cs → 全部自動加入,因此當我們新增檔案時,直接新增就能用

所謂的 什麼是 SDK-style .csproj 是說,它把原本冗長的建置設定「收進一個 SDK 模板」,讓你只寫差異,其他全部用預設幫你做好。當我們開 .csproj 檔案時,會看到
1 | <Project Sdk="Microsoft.NET.Sdk"> |
這就是 SDK-style 的標誌,而這 SDK 其實在安裝 .NET SDK 時就已經存在了

type could not be found
有時候我們會遇到這個錯誤,但可能性有幾個
- using 錯?
- namespace 錯?
- reference 壞掉?
但實際上有可能是我們在開發時,那個型別根本沒被編進 dll!

Visual Studio
在 Visual Studio 中透過「加入新項目」或「加入現有項目」新增檔案時,VS 會自動在 csproj 補上 <Compile Include>
- Solution Explorer → 找到對應專案
- 右鍵 → 加入 → 現有項目
- 選取該
.cs檔案
而這就是 IDE 的方便之處

packages.config
packages.config 是 .NET Framework 專案(舊式 csproj)用來記錄 NuGet 套件相依關係的 XML 檔案,位於每個專案的根目錄
1 |
|
每個 <package> 記錄
id:NuGet 套件名稱version:確切版本號targetFramework:目標 .NET 版本
packages.config vs PackageReference(.NET Core)
| 項目 | packages.config(.NET Framework) | PackageReference(.NET Core / .NET 5+) |
|---|---|---|
| 格式 | 獨立的 XML 檔案 | 直接寫在 .csproj 裡 |
| 套件存放 | packages\ 資料夾(在 repo 旁邊) |
NuGet 全域快取(~/.nuget/packages) |
| 相依解析 | 每個專案獨立列出所有直接 + 間接相依 | 自動解析,只需列直接相依 |
| 版本衝突處理 | 手動 binding redirect | 自動統一版本 |
packages\ 資料夾
packages.config 宣告的套件會被下載到 solution 根目錄的 packages\ 資料夾:
1 | nineyi.scm.nmqv2\ |
注意:不同版本的套件可以同時存在 packages 資料夾,但 build 時只有一個版本的 dll 會進 bin

版本衝突是怎麼發生的
案例
1 | 整個 solution 的 packages.config 分布: |
分析
packages.config= 這個專案用了哪些 NuGet 套件、哪個版本- 每個專案有自己的
packages.config,同一 solution 不同專案可以宣告同一套件的不同版本 - Build 時所有 dll 複製到同一個 bin,版本衝突是無聲的,不會有編譯錯誤
- 版本衝突只在 runtime 才爆出來,而且通常是難以追蹤的環境相關錯誤
- .NET Core / .NET 5+ 的 PackageReference 格式改善了這個問題,版本解析更智慧,因為他把「每個專案各自決定版本」改成「整個建置過程統一算出一個一致版本」,衝突在 build 階段就被攔下來
在舊的 packages.config 模式,每個專案都有自己的套件版本清單,Build 時只是「各自把 DLL 複製到 bin」
1 | A 專案 → Newtonsoft.Json 10 |
最後 bin 裡可能只剩一個版本(誰蓋掉誰不一定)

而在 PackageReference 模式(.NET Core / .NET 5+),套件直接寫在 .csproj
1 | <ItemGroup> |

Build 時,MSBuild 會先「解析整個依賴樹」(dependency graph)包含直接引用的套件、套件依賴的套件(transitive dependencies),接著做「版本決策」(dependency resolution),同一個套件只能選一個版本,而常規則是優先選「最高相容版本」或遵守你明確指定的版本,如果版本衝突會導致無法解析直接 build 失敗,最後才還原(restore)套件並編譯,而且 DLL 不再隨便散在專案資料夾,而是集中在全域快取(global packages folder)
1 | Step1:偵測到 Razor 2.0 vs 3.0 |


