發行 .NET Core 應用程式的時候有兩種方式,一種叫做 FDD (Framework Dependent Deployment 框架相依部署),另一種叫 SCD (Self Contained Deployment 自封式部署),後者可以將應用程式封裝成單一可執行檔,這篇將分享一些 .NET Core 應用程式封裝成單一可執行檔時,我們可以用於優化檔案大小的設定。
以下範例會用一個單純的主控台應用程式來示範,順便比較一下幾種設定所封裝出來的檔案大小為何,程式碼如下:
1 | class Program |
執行模式
作為可以跨平台的 .NET Core 的應用程式,如果我們要發行到不同平台環境時,在編譯前是需要先指定之後要在那些平台環境執行,在做自封式部署時也是需要同樣的設定,而這裡所稱的「平台」代表兩件事情:
- .NET 執行時期所需要的環境,例如 .NET Framework、.NET Core
- 執行 .NET 應用程式時的作業系統環境,例如 Windows X64、MacOS、Linux
對應到 .csproj 專案檔內的設定,就是指 TargetFramework 和 RuntimeIdentifier 這兩個設定,各種對應不同環境的設定值請參考下面設定值參考資料:
- 設定
<TargetFramework>設定值參考資料:TFM 目標 Framework - 設定
<RuntimeIdentifier>設定值參考資料:.NET Core RID Catalog
如果要設定多目標或多執行環境時,可將名稱修改成
<TargetFrameworks>和<RuntimeIdentifiers>,設定值可以用;區隔,代表多個設定值。
設定的範例如下:
1 | <Project Sdk="Microsoft.NET.Sdk"> |
封裝成單一可執行檔
封裝成單一可執行檔的優點就不贅述了,基本就是一個檔案帶著走,到哪都可以執行,不用擔心該環境有沒有對應版本的 .NET Runtime。
要將專案設定成自封式部署並封裝成單一可執行檔,基本做兩件事情,設定執行時期的環境,以及設定發行時使用單一檔案,因此我們在 .csproj 專案檔內加上 PublishSingleFile 設定即可,程式碼如下:
1 | <Project Sdk="Microsoft.NET.Sdk"> |
如果想在發行時使用指令的方式設定也可以,
dotnet publish -r win-x64 -p:PublishSingleFile=true此指令和上面修改專案檔的效果式一樣的。
這種方式將 .NET 執行環境以及該應用程式封裝成單一執行檔,編譯後的檔案約為 67 MB,以一個 HelloWorld 應用程式來說,檔案大小老實說滿大的,但他畢竟將整個執行環境封裝進去了。

移除沒用到的部分
前面之所以檔案會這麼大,他除了把整個 .NET 執行環境都包進去外,也包進了不少應用程式根本沒用到的功能,所以實際上是有一點虛胖的,所幸我們可以增加一個設定,使用 Assembly Trimming 的方式,透過 IL Linker 取出應用程式所需要的部分,藉此塑身一下。
設定方式只要在 .csproj 專案檔內加上 PublishTrimmed 設定即可:
1 | <Project Sdk="Microsoft.NET.Sdk"> |
如此一來編譯後的檔案就縮小成 25.8 MB,算是還可以了。

另外在 .NET Core 3 之後,多了一個 PublishReadyToRun 設定,這個設定會將應用程式編譯成 Ahead-Of-Time (AOT) 模式的格式,讓應用程式啟動得更快,這個方式減少 Just-In-Time (JIT) 編譯器在應用程式載入時所需執行的工作量,藉此來改善啟動效能。
但 AOT 模式有個缺點,就是編譯後的檔案會稍微大一點,因為他保留了 JIT 會用到的 IL 碼,確保在某些執行情境不用出問題。
設定 PublishReadyToRun 後的編譯大小約為 26 MB,如下:

CoreRT
封裝成單一可執行檔的最終幻想就是編譯成 Native 程式,並享有檔案夠小、執行夠快的可執行檔,目前 .NET 團隊是有一個開放原始碼專案 CoreRT,可以將 .NET Core 編譯為沒有相依 CLR 的 Native 程式,簡單說就是編譯成機器碼(如同 C 和 C++ 一樣),而不是 IL 碼,因此 CoreRT 不是 CLR/CoreCLR,因為他不是虛擬機器也不包含 JIT,這代表它沒有即時產生、執行程式碼的功能。
要將目前執行的範例改用 CoreRT 來編譯,要做一些調整,首先 .csproj 專案檔要將上面加入的自封式部屬設定拿掉,但要保留目標執行環境的設定,接著安裝 Microsoft.DotNet.ILCompiler 套件,這個套件在 NuGet.org 是找不到的,你必須先增加 https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json 這個 NuGet 套件來源,我這邊是直接在專案資料夾中加入 nuget.config 檔來做設定。
完成後的 .csproj 專案檔內容如下:
1 | <Project Sdk="Microsoft.NET.Sdk"> |
你應該會注意到
Microsoft.DotNet.ILCompiler的版本號有alpha關鍵字,目前還是有多限制,因此請不要用在正式環境中。

使用 CoreRT 來編譯的檔案大小出乎意料的還可以要 6.9 MB(畢竟如果是 C 了話檔案應該只有幾 K),但這和前面的結果相比已經只剩下 1/4 左右的大小了,期待它未來的發展。
如果你對 CoreRT 最佳化有興趣了化,可以參考官方這份文件 Optimizing programs targeting CoreRT,裡面還有一些設定可以玩,例如我加上 IlcInvariantGlobalization 這組移除文化特性的設定,可以再少個 100 KB 左右 😄

目前 CoreRT 還沒有邁入正式發行的計畫,因此連 Roadmap 都沒有,用在個人 Side Project 無所謂,但建議不要用在正式環境中。
以上就是最近在玩自封式部屬的一些心得筆記。
本篇完整範例程式碼請參考 poychang/Demo-Reducing-Size-App。
後記
上面有提到在專案資料夾中加入 nuget.config 檔,來增加 NuGet 套件來源,就是增加下面 dotnet-core 這組來源,這同時也式 .NET Team 在使用的每日建置版本,當然除了 .NET Core 有這樣每日建置的來源外,還有其他各種框架的搶鮮版套件來源,請考下面 nuget.config 檔內容:
1 | <?xml version="1.0" encoding="utf-8"?> |
參考資料: