.NET MAUI Windowsアプリのexeサイズが大きい原因と小さくする方法|PublishTrimmedで起動しない対策とdotnet publish例

.NET MAUIでWindows向けに発行(publish)すると、exeや配布フォルダが想像以上に大きくなりがちです。多くの場合は「自己完結型(self-contained)」で.NETランタイムやWindows App SDK、NuGet依存関係まで一式を同梱しているのが主因です。ここでは、サイズが増える仕組み、縮小の現実的な手段、PublishTrimmedで起動しない理由、そして失敗しにくいdotnet publishの組み立て方を整理します。

日程Fit。無料・登録不要。「いつ空いてる?」を、ひとつのリンクで。リンクを送って、○△×でかんたん日程調整。無料で日程を作る。
目次

.NET MAUI(Windows)のexeが大きくなるのは「仕様寄り」な理由がある

まず押さえたいのは、.NET MAUIのWindowsアプリで「exeだけが小さく、配布は軽い」というWin32時代の感覚は基本的に当てはまらないことです。特に次の条件が重なると、配布物全体のサイズは一気に増えます。

  • 自己完結型配布(self-contained)にしている(=.NETランタイム同梱)
  • Windows App SDKを自己完結(WindowsAppSDKSelfContained=true)にしている(=WinUI 3/Windows App SDKのランタイム同梱)
  • サードパーティ製のNuGet(UI/DI/JSON/DB/暗号化/ログなど)を多数使っている(=DLL・ネイティブ依存・リソース増)
  • 画像・フォント・ローカライズ・WebViewなど、リソース系が厚い
増える要因中身として増えるものなぜ増えるか縮小の方向性
Self-contained(.NET同梱).NETランタイム一式(多数のdll等)実行環境に.NETが無くても動くようにするため要件が許せばFramework-dependentへ
WindowsAppSDKSelfContained=trueWindows App SDKのフレームワークパッケージ展開物Windows App SDKのランタイムをアプリ側に含めるため要件が許せばFramework-dependentへ
NuGet依存が多い参照DLL、ネイティブ依存、リソース機能の対価として同梱物が増える依存の棚卸し、軽量ライブラリへ置換
単一ファイル化(Single-file)埋め込み・抽出用の仕組み「1ファイル」にまとめる代わりに内部へ詰め込む圧縮や抽出設定の調整
トリミング(Trimming)(減る)未使用コード静的解析で「不要」を削る対応できれば大きく効くが難易度高め

つまり「大きい=失敗」ではなく、配布要件(インストール不要で動かしたい、社内PC限定、ストア配布など)に合わせてどこまで同梱するかの設計問題になりやすい、というのが現実です。

配布形態を理解すると、サイズの“増えどころ”が見える

.NET MAUI(Windows)をCLIでアンパッケージ(MSIXなし)として発行する場合、Microsoft Learnの手順では、概ね次の指定が前提になっています。

  • -p:WindowsPackageType=None:アンパッケージで出す(MSIXではない)
  • -p:WindowsAppSDKSelfContained=true:Windows App SDKを自己完結で同梱する
  • -p:RuntimeIdentifierOverride=win10-x64(またはx86):特定の既知問題回避のため

Windows App SDK側のドキュメントでも、プロジェクトは既定でframework-dependentであり、自己完結にするにはWindowsAppSDKSelfContainedをtrueにし、Windows App SDKのフレームワークパッケージが出力に展開されアプリに同梱される、と説明されています。また「.NETアプリは完全に自己完結にするなら、.NET側もself-containedとしてpublishする必要がある」と明記されています。

この2つ(.NET自己完結+Windows App SDK自己完結)が揃うと、サイズが増えるのは自然な結果です。逆に言えば、サイズを大きく減らしたいなら「同梱をやめる」以外に決定打は少ない、ということでもあります。

あなたのdotnet publishコマンドは概ね方向性は合っている(ただし“つなぎ方”が重要)

CLIでアンパッケージのMAUI Windowsアプリをpublishする際、パラメータの意味は次の整理になります。

指定役割ハマりどころ
-f netX.Y-windows10.0.19041.0Windows向けTFMを指定csprojの<TargetFrameworks>と一致させる必要がある
-c ReleaseReleaseでpublish.NET 8ではpublishの既定がReleaseのため省略可という注記あり
-p:WindowsPackageType=Noneアンパッケージ(MSIXなし)ソリューション全体をpublishすると問題になりやすいので、アプリのcsprojにスコープする
-p:WindowsAppSDKSelfContained=trueWindows App SDK同梱これをtrueにすると出力物が増える(が依存インストール不要になる)
-p:RuntimeIdentifierOverride=win10-x64既知問題回避として推奨される指定x64/x86をターゲットに合わせる
-p:PublishTrimmed=true未使用コード削除でサイズを減らす反射・動的ロードが絡むと起動不能/機能欠けになりやすい(後述)

そして、実務で多いミスが「改行した結果、オプションが同一コマンドに渡っていない」パターンです。-p:PublishTrimmed=trueを別行に書く場合、シェルごとの行継続が必要です。確実なのは、まずは1行で書いて動作確認することです。

まずは1行で確実に通す(推奨)

dotnet publish .\YourApp\YourApp.csproj -f net9.0-windows10.0.19041.0 -c Release -p:RuntimeIdentifierOverride=win10-x64 -p:WindowsPackageType=None -p:WindowsAppSDKSelfContained=true -p:PublishTrimmed=true

また、Microsoft Learnでは「ソリューションをpublishすると各プロジェクトを個別にpublishしようとして問題が起きやすいので、MAUIアプリのプロジェクトにスコープすべき」と警告しています。上の例のようにcsprojを明示するのが安全です。

Windows(cmd.exe)の行継続例

dotnet publish .\YourApp\YourApp.csproj -f net9.0-windows10.0.19041.0 -c Release ^
  -p:RuntimeIdentifierOverride=win10-x64 ^
  -p:WindowsPackageType=None ^
  -p:WindowsAppSDKSelfContained=true ^
  -p:PublishTrimmed=true

PowerShellの行継続例(バッククォート)

dotnet publish .\YourApp\YourApp.csproj -f net9.0-windows10.0.19041.0 -c Release `
  -p:RuntimeIdentifierOverride=win10-x64 `
  -p:WindowsPackageType=None `
  -p:WindowsAppSDKSelfContained=true `
  -p:PublishTrimmed=true

この“つなぎ方”の問題があると、PublishTrimmedが効いていない(あるいは別コマンドとして実行されて失敗している)のに気づきにくく、調査が長引きます。

サイズ削減は「どこまで同梱するか」を先に決めるのが近道

サイズ削減の戦略は大きく3つに分かれます。順番に検討するほど失敗しにくいです。

依存関係(NuGet)を棚卸しする

トリミングに手を出す前に、まず“不要な機能を同梱していないか”を点検します。たとえば、UI一式のライブラリ、巨大なORM、フル機能のPDF/Excel処理などは、少し使うだけでも配布物が重くなりがちです。

  • dotnet list packageで依存を一覧化し、使っていないものを外す
  • 「便利だから」で入れたユーティリティを標準APIへ置換できないか検討する
  • 同系統のライブラリが複数入っていないか(JSON/DI/ログ等)確認する

この段階はトリミングより安全で、効果も読みやすいです。

Framework-dependentに寄せる(要件が許せば最も効く)

配布先が社内PCや管理端末で「前提ランタイムをインストールできる」なら、.NETランタイムWindows App SDKを端末側に用意し、アプリはframework-dependentに寄せるのが最もサイズが下がります。Windows App SDKのドキュメントでも、既定はframework-dependentであることが示されています。

方針配布物サイズ導入の手間向いているケース
Self-contained(全部同梱)大きい小さい(配るだけで動く)個人配布・不特定多数・インストールさせたくない
Framework-dependent(ランタイムは端末側)小さい大きい(事前インストールが必要)社内配布・管理端末・インストーラーで前提を整えられる

「サイズを絶対に小さくしたい」が最優先なら、ここが最も現実的な落としどころになります。

トリミング(PublishTrimmed)で削る(ただし難易度は高い)

トリミングは、リンク時に未使用コードを削ってサイズを下げる仕組みです。.NET MAUIではILLinkが中間コードを解析し、未使用のメンバーや型を取り除く、と説明されています。さらに、トリミング互換性は静的解析(trim warnings)で警告され、警告がある場合は修正し、十分にテストすべき、とされています。

そして重要なのが、.NETのトリミングは自己完結型(self-contained)でのpublishでのみサポートされる点です。<PublishTrimmed>true</PublishTrimmed>を指定すると、self-contained publishでトリミングされたアプリを生成し、ビルド中に互換性警告を出す、とされています。

dotnet publish -r win-x64 -p:PublishTrimmed=true

MAUI特有の観点としては、TrimMode(partial/full)で挙動が変わり、fullでは「文字列で指定するバインディングパス」など、フル・トリミングと相性が悪い機能が削除され得ることも明記されています。つまり、アプリ側の書き方や機能選択がトリミング可否に直結します。

PublishTrimmed=trueで起動しなくなる“典型原因”

結論から言うと、起動しないのは「トリマーが必要なコードまで消してしまった」可能性が高いです。トリマーは静的解析で参照関係を追いますが、次のようなパターンは解析が困難(または不可能)で、実行時に必要な型・メソッドが削られやすくなります。

起動不能の典型パターンなぜ危ないかよくある兆候
文字列で型名を指定して動的生成(例:Type.GetType)実行時入力に依存し、必要な型を事前に推論できない起動直後に例外、画面が出ない
Assembly.Load/LoadFromでプラグインDLLを読むロードされるアセンブリが何を参照するか分からず、トリム済みで欠ける特定機能だけ落ちる、プラグインが認識されない
リフレクションが濃いシリアライザー(例:XmlSerializer等)複雑な反射で型を走査し、トリマーが追跡できない設定読み込み、保存、API通信で落ちる
.NET MAUIの“full trimming非互換機能”を使っているfull trimmingでは機能が削除される可能性があるバインディング/ナビゲーション/特定UI機能が動かない

.NETのトリミング警告(例:IL2026)の説明では、RequiresUnreferencedCodeが付いたメンバーを呼び出す(または反射でアクセスする)と警告になる、とされ、代表例としてAssemblyを動的に読み込むケースXmlSerializerのように複雑な反射を行うケースが挙げられています。これはまさに「トリムで必要なメンバーが消える」典型パターンです。

トリミングで壊れたときの現実的な直し方

「トリミングを有効にしたら起動しない」を解くには、精神論ではなく、どこがトリム非互換なのかを特定し、残す/書き換える/諦めるの三択になります。

まずは“警告を増やす”

.NET MAUIのトリミングドキュメントでは、パッケージ由来の警告が「アセンブリごとに最大1件」に抑えられることがあり、より詳細にしたい場合はTrimmerSingleWarnをfalseにすることで個別警告を有効にできる、と説明されています。警告が少なすぎて手掛かりがないときに有効です。

<PropertyGroup>
  <TrimmerSingleWarn>false</TrimmerSingleWarn>
</PropertyGroup>

“根本的に無理なパターン”は隔離する

.NETのガイドでは、実行時入力(文字列)から型を取得して生成するようなコードはトリミング互換にできない例として示され、そうした箇所はRequiresUnreferencedCodeを付けて「ここはトリミング非互換」と明確にする手順が紹介されています。これにより、呼び出し側に警告が伝播し、危険箇所が追いやすくなります。

MAUIアプリで言えば、プラグイン方式・動的XAMLロード・リフレクション前提のDI/シリアライズなどが該当しやすいです。これらを「使わない設計に寄せる」か「トリミングしない(もしくは部分的に留める)」のが、時間対効果の高い判断になります。

どうしても残したい型・メンバーは“残す指示”を出す

MAUIのトリミングドキュメントでは、トリマーが動的呼び出し(間接的なものも含む)で必要なコードを削除することがあるため、DynamicDependencyで保持するメンバーを指示できる、と説明されています。例として、動的にアセンブリを読み、型・メソッドを取得して呼ぶコードに対し、DynamicDependencyがあることで対象メソッドを保持できることが示されています。

ただし、保持指定はメンテナンスコストが上がりやすいのが難点です。依存ライブラリ更新で型名・メンバーが変わると再度壊れます。チーム開発では「保持指定を増やしすぎない」ルール作りが重要になります。

.NET MAUI側の“trim不利な機能”を避ける

MAUIドキュメントでは、full trimmingと相性が悪い機能として、文字列で指定するバインディングパス、LoadFromXamlによる実行時XAMLロード、QueryPropertyAttributeによるナビゲーションデータ受け取り等が列挙され、代替(compiled bindings、IQueryAttributableなど)に置き換えることが推奨されています。設計を寄せられるなら、ここが最も効きます。

トリミング以外でもできる「見た目のサイズ削減」

トリミングは強力ですが、難易度が高いのも事実です。そこで、壊しにくい順に“できること”をまとめます。

Single-file化と圧縮(配布体験を良くする)

.NETには単一ファイル(single-file)で配布する仕組みがあり、<PublishSingleFile>true</PublishSingleFile>でself-contained publish時に単一ファイルを生成できる、とされています。また、single-fileには互換性警告が出るため、ビルド時の分析にも役立ちます。

<PropertyGroup>
  <PublishSingleFile>true</PublishSingleFile>
</PropertyGroup>

さらに、single-fileは埋め込むアセンブリを圧縮する設定があり、EnableCompressionInSingleFileをtrueにすると埋め込みアセンブリが圧縮され、exeサイズを大きく減らせる可能性がある一方、起動時に展開が走るため起動コストが増え得るので測定が推奨されています。

<PropertyGroup>
  <PublishSingleFile>true</PublishSingleFile>
  <EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
</PropertyGroup>

またsingle-fileでも、既定ではコアランタイム等のネイティブDLLが別ファイルになることがあり、完全に1ファイルに寄せたい場合はIncludeNativeLibrariesForSelfExtractIncludeAllContentForSelfExtractといったプロパティが紹介されています(抽出方式になる点に注意)。

ただし、.NET MAUI+Windows App SDK+アンパッケージという組み合わせでは、環境や依存関係によって「期待通りの単一exe」にならない・互換性問題が出ることもあります。まずは“単一ファイルは配布体験改善の手段”として捉え、サイズ・起動速度・安定性のバランスで判断するのがおすすめです。

ReadyToRunは“サイズを減らす”目的には向かない

起動を速くするためのReadyToRun(R2R)は、ILとネイティブコードの両方を含むためバイナリが大きくなり、一般にアセンブリサイズが2〜3倍に増える、と説明されています。したがって「サイズ削減」の目的で有効化するものではなく、起動速度が要件のときに検討するオプションです。

“サイズを小さくしたい”ときのチェックリスト(実務向け)

  • 配布要件を確定する:インストール不要が必須か/社内配布で前提導入できるか
  • publish対象はcsprojを明示:ソリューションpublishは避ける
  • 依存関係を棚卸し:使っていないNuGetを削る、重い依存を置換する
  • 同梱をやめられるならやめる:.NET/Windows App SDKをframework-dependentに寄せる
  • トリミングは段階的に:まず警告を増やす→危険箇所を特定→残す指示 or 書き換え or 断念
  • 単一ファイル+圧縮は“配布改善”として試す:サイズと起動時間を両方測る

まとめ:.NET MAUI(Windows)のサイズ問題は「削る」より「同梱を設計する」

.NET MAUIのWindowsアプリで配布物が大きくなる主因は、自己完結型配布による.NETランタイム同梱と、Windows App SDK自己完結(WindowsAppSDKSelfContained=true)によるランタイム同梱、そして多数のNuGet依存です。CLIのアンパッケージ発行は、Microsoft Learnの手順に沿ってWindowsPackageType=NoneRuntimeIdentifierOverrideを適切に指定し、publish対象はアプリのcsprojにスコープするのが安全です。

サイズを小さくしたい場合、最も効くのは「前提導入できるならframework-dependentへ寄せる」ことです。どうしても同梱が必要なら、次に効くのがトリミングですが、反射・動的ロード・一部MAUI機能が絡むと起動不能になり得ます。警告を増やして原因を特定し、残す指定や設計変更ができない場合は、無理にトリミングを続けず安定性を優先する判断も重要です。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次