.NET MAUIで「デバッグ実行は正常なのに、Release(非デバッグ)だと起動直後に無言で終了する」症状は、AOTだけに原因があるとは限りません。AOT無効化が効かなかった実例と、最終的にColor Converterの差し替えで解決した経緯をもとに、切り分けと再発防止をまとめます。
現象:Debugでは動くのにReleaseで起動直後に落ちる(例外メッセージなし)
今回のケースは、.NET MAUIアプリがDebug実行だと正常動作する一方で、Release実行(非デバッグ)では起動後すぐ終了してしまう、というものです。厄介なのは、ユーザー操作の前に落ちる上に、画面上に例外も出ず、Visual Studioの出力にも目立ったエラーが出ないことがある点です。
| 項目 | 内容(今回の状況) | 読み取れること |
|---|---|---|
| 症状 | DebugはOK/Releaseは起動直後に無言で終了 | Releaseで有効になる最適化・トリミング・構成差分が疑わしい |
| 試したこと | AOT無効化のプロパティをcsprojへ追加 | 「AOTが怪しい」という仮説で設定を変更 |
| 例外 | 画面表示なし・メッセージなし | ログを取りにいかないと原因が見えない可能性 |
| 環境 | Windows 11/Visual Studio 17.13.2/.NET 8・.NET 9/MAUI 9.0.14 → 9.0.40 | ワークロードやSDK、VSの状態の影響もあり得る |
「Releaseでだけ落ちる」問題は、原因が1つとは限りません。特にMAUIはXAML・リソース・バインディング・プラットフォーム差分が絡みやすく、さらにReleaseは最適化やトリミング(未使用コード削除)が影響することがあります。
まず整理:DebugとReleaseで何が違うのか
闇雲に設定をいじる前に、DebugとReleaseの「差分」を把握すると切り分けが速くなります。
| 差分ポイント | Debugで起きがちなこと | Releaseで起きがちなこと | 代表的な症状 |
|---|---|---|---|
| 最適化(最適化ON/OFF) | 最適化が弱く、挙動が素直 | 最適化が強く、未定義動作・タイミング差が顕在化 | タイミング依存のクラッシュ/初期化順の問題 |
| ログ・例外の見え方 | デバッガーが例外で止まる | 未処理例外がそのままプロセス終了、画面に出ないことも | 「無言で落ちる」 |
| トリミング(リンク) | 無効・弱いことが多い | 有効になる構成・ターゲットがある(特にモバイル) | 反射・XAML・コンバーターが効かない/ページが開かない |
| AOT(事前コンパイル) | 通常は無効・一部のみ | ターゲットによって有効化され得る | 特定端末だけ落ちる/起動直後に終了など |
ここで重要なのは、「Releaseだけ落ちる」=「AOTが原因」ではないという点です。AOTは確かに有力候補になり得ますが、同じくらいトリミング、XAML、コンバーター、依存ライブラリ、開発環境が原因になり得ます。
今回の結論:AOTが原因ではなかった
今回のやり取りでは、AOTを疑って以下のような設定を試したものの改善しませんでした。
<RunAOTCompilation>False</RunAOTCompilation><EnableAOTCompilation>False</EnableAOTCompilation><AotAssemblies>False</AotAssemblies>
しかし回答側で、質問者のGitHubプロジェクトをそのままWindows / Android のReleaseビルドで検証したところ、特に設定を変えなくても正常動作した、という結果でした。さらに、Microsoft.Maui.Controlsを9.0.40へ更新しても問題が再現しなかったため、AOT起因よりも質問者の開発環境(Visual Studio / SDK / Workload / キャッシュなど)側に原因がある可能性が高い、という判断になります。
この時点での現実的な対処としては、次のような「環境メンテ」が有力です。
- Visual Studioを最新へ更新
- Visual Studio Installerから修復(Repair)を実行
- .NET SDK / MAUI Workloadの更新・修復
最終的な自己解決:Color Converterを差し替えたらReleaseでも動いた
そして決定打になったのが、質問者側での自己解決です。試行錯誤の末、外部からコピーした「色変換用コンバーター(Color Converter)」を使うと、Releaseでも正常に動作するようになった、という結論でした。
状況としては、特定ページ(DetailPage)がRelease時だけ表示されないという現象があり、コンバーターを差し替えると改善したため、XAMLで使っていたカラー変換・バインディング周りがRelease構成で問題を起こしていた可能性が高い、という推測が成り立ちます。
| 疑っていたこと | 実際に起きていたこと | 学び |
|---|---|---|
| AOTが原因でReleaseが落ちる | 別環境ではReleaseでも正常動作 | 「自分のPCだけ再現」は環境・キャッシュ・SDK差分も疑う |
| csprojでAOTを切れば直る | AOT無効化設定を入れても改善しない | 設定の有効範囲(構成/TFM/プラットフォーム)確認が必要 |
| 原因不明の無言終了 | コンバーター差し替えで改善 | 「XAML/Converter/Resource」を最小化して切り分けるのが最短 |
このケースは、AOT云々よりもむしろ「Release時にだけ顕在化するXAML・コンバーターの不整合」という、MAUIでよくあるタイプに近いです。
「無言で落ちる」を終わらせる:まずログを取れる状態にする
Releaseで落ちる問題は、原因を推測するより「ログを出して事実を見る」ほうが圧倒的に早いです。特に、デバッガーが付いていないと例外ダイアログが出ないケースでは、ログが唯一の手がかりになります。
Windows(WinUI)での確認ポイント
- イベントビューアー:Windowsログ → アプリケーション で .NET Runtime / Application Error を確認
- Visual Studioの出力:Releaseで起動した際の出力ウィンドウを確認
- クラッシュの瞬間の例外:未処理例外が記録されていないかを見る
Androidでの確認ポイント(adb logcat)
Androidは、画面に何も出なくてもログには出ていることが多いです。Releaseビルドで落ちるなら、次を優先します。
- adb logcatでアプリ起動直後の例外を追う(特に
AndroidRuntime/FATAL EXCEPTION) - ビルド・署名の違いで落ちる場合、パーミッションやリソース参照エラーも疑う
Releaseでも例外を「必ずログに残す」最小の仕込み
アプリ全体の未処理例外を拾うだけでも、「無言終了」から脱出できる確率が上がります。MAUIアプリの起動時(例:MauiProgram.CreateMauiApp() 付近や App の初期化)で、最低限以下を仕込みます。
using System.Diagnostics;
public static class CrashLog
{
public static void Register()
{
AppDomain.CurrentDomain.UnhandledException += (s, e) =>
{
Write("UnhandledException", e.ExceptionObject as Exception);
};
TaskScheduler.UnobservedTaskException += (s, e) =>
{
Write("UnobservedTaskException", e.Exception);
e.SetObserved();
};
}
private static void Write(string title, Exception? ex)
{
try
{
var msg = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {title}\n{ex}\n";
Debug.WriteLine(msg);
// 必要ならファイルにも書く(WindowsならLocalAppDataなど)
// File.AppendAllText(path, msg);
}
catch
{
// ログすら書けない状況でも落とさない
}
}
}
そして起動時に呼び出します。
public static MauiApp CreateMauiApp()
{
CrashLog.Register();
var builder = MauiApp.CreateBuilder();
// ...
return builder.Build();
}
これで「落ちる瞬間に何が起きたか」を追いやすくなります。特にXAMLの初期化例外は、デバッガーなしだと見逃されやすいので効果的です。
切り分けの鉄則:Release固有の要素を“1つずつ”OFFにして戻す
Debug/Release差分の切り分けは、犯人探しを「推理」から「実験」に変えるのがコツです。次の表の順で潰すと、遠回りしにくくなります。
| 手順 | やること | 狙い | 判断 |
|---|---|---|---|
| 1 | 別PC/別環境で同じプロジェクトをRelease実行 | 環境依存かコード依存かを即判定 | 別環境で動くなら「自環境or構成差分」が濃厚 |
| 2 | bin/obj削除→クリーンビルド | キャッシュ・古い生成物を排除 | 直れば生成物やキャッシュ汚染が原因 |
| 3 | Releaseでトリミングを一時OFF | リンク/トリミング起因の不具合を確認 | OFFで直るならトリミング対策が必要 |
| 4 | ReleaseでAOT関連を一時OFF | AOT起因の可能性を確認 | OFFで直るならAOT/ネイティブ周りを深掘り |
| 5 | 落ちるページ(例:DetailPage)のXAMLを最小化 | XAML/Resource/Converterのどれが犯人か切り分け | 最小化で直るなら、戻しながら犯人を特定 |
今回の自己解決は、まさに「手順5」を実行した結果に近いです。ページ単位で疑わしい要素(特にConverter)を外し、戻しながら特定するのが最短でした。
AOTを疑う場合:設定は“対象構成”に正しく入れる
AOTを疑って一時的に無効化したい場合は、Debug/Releaseのどちらに効かせたいのかを明確にした上で、.csprojに構成条件付きで入れます。
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<RunAOTCompilation>false</RunAOTCompilation>
<EnableAOTCompilation>false</EnableAOTCompilation>
<AotAssemblies>false</AotAssemblies>
</PropertyGroup>
ただし注意点があります。
- プロパティ名や有効範囲は、ターゲット(Windows/Android/iOS)やSDKの世代で変わることがあります。
- 「設定を書いたのに効かない」場合、そもそもそのターゲットでは参照されないプロパティである可能性があります。
- 反映されているかは、ビルドログ(詳細ログ)や生成されたMSBuildプロパティで確認するのが確実です。
今回のケースでは、AOTを切っても改善しなかったため、AOT起因ではない(少なくとも主要因ではない)と判断できます。
Releaseだけ落ちるなら「トリミング(リンク)」を最優先で疑う
MAUIでReleaseだけ不調になるとき、AOTより先に疑うべき代表格がトリミング(未使用コード削除)です。特に以下のような要素は、トリミングと相性が悪いことがあります。
- 反射(
Type.GetType、Activator.CreateInstanceなど) - XAMLから動的に生成されるクラス(Converter、MarkupExtensionなど)
- 外部ライブラリが内部で反射を使っている
切り分けとして、まずは一時的にトリミングをOFFにして挙動が変わるか確認します。
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<PublishTrimmed>false</PublishTrimmed>
</PropertyGroup>
これで直るなら、次は「トリミングをONに戻しつつ、消されると困る型・メンバーを守る」方向に進みます。守り方はいくつかありますが、現場でやりやすいのは次のどれかです。
| 対策 | 概要 | 向いているケース | 注意 |
|---|---|---|---|
| コードから明示参照する | 使う型をコード上で直接参照し、トリマーに「必要」と認識させる | ConverterやServiceなど、数が少ない | 参照漏れがあると再発 |
| トリミング注釈(属性)を付ける | 動的アクセスするメンバーを残す指定を行う | 反射を避けられない | 属性の理解が必要、適用範囲に注意 |
| 設定を段階的に弱める | Trimモードやリンク範囲を調整して影響を減らす | 短期的に出荷を優先したい | アプリサイズ増加、効果が限定的なことも |
今回の自己解決(Color Converter差し替え)は、「トリミングに弱い実装を、トリミング耐性のある実装に置き換えた」可能性も十分あります。
要注意:Color Converter / バインディングがReleaseでだけ壊れる典型パターン
「特定ページ(例:DetailPage)がReleaseでだけ表示されない」「起動直後に落ちる」といった症状は、XAML初期化時に例外が起きているパターンがよくあります。特にConverter周りは、次の落とし穴が多いです。
戻り値の型が一致していない
たとえば、プロパティがColorを要求しているのに、ConverterがstringやBrushを返してしまう。Debugでは気付きにくくても、ReleaseではXAMLコンパイルや最適化の影響で例外が表面化することがあります。
nullや例外を握りつぶしている
Converter内部で例外が起きているのに、catchして何も出さずにnullを返す、など。結果としてページ初期化が失敗し、アプリが終了することがあります。少なくとも切り分け中は、catchしたらログに吐くのが安全です。
XAMLから動的生成されるConverterがトリミングで消える
ConverterをXAMLのリソースとして定義している場合、構成や環境によっては「参照が弱い」と判断されて削除される可能性があります。特に名前空間指定・型解決に癖があると、Releaseでだけ「型が見つからない」→初期化例外、になりがちです。
自作の色変換が“プラットフォーム差分”を踏んでいる
MAUIの色はMicrosoft.Maui.Graphics.Colorが中心ですが、うっかりSystem.Drawing系やWindows専用APIに依存した変換をすると、ターゲットや構成差で破綻しやすくなります。Debugでは偶然動いても、Releaseで落ちる引き金になります。
安全寄りのColor Converter例(自作するならここから始める)
自作Converterを使うなら、まずは「変換が失敗しても落とさない」「ログで追える」「戻り値の型が明確」という形にしておくと、Releaseクラッシュの芽を摘みやすいです。
using System.Globalization;
using Microsoft.Maui.Graphics;
public sealed class HexToColorConverter : IValueConverter
{
// #RRGGBB / #AARRGGBB を想定(必要に応じて拡張)
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
try
{
if (value is null) return Colors.Transparent;
var s = value.ToString()?.Trim();
if (string.IsNullOrEmpty(s)) return Colors.Transparent;
// MAUIのColor.FromArgbは "#RRGGBB" / "#AARRGGBB" を扱える
if (s[0] != '#') s = "#" + s;
return Color.FromArgb(s);
}
catch (Exception ex)
{
// 切り分け中は必ずログへ
System.Diagnostics.Debug.WriteLine(ex);
return Colors.Transparent;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> throw new NotSupportedException();
}
XAMLでの利用例です。
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:YourApp.Converters">
<ContentPage.Resources>
<ResourceDictionary>
<local:HexToColorConverter x:Key="HexToColorConverter" />
</ResourceDictionary>
</ContentPage.Resources>
<Label
Text="Sample"
TextColor="{Binding AccentHex, Converter={StaticResource HexToColorConverter}}" />
</ContentPage>
ポイントは次の3つです。
- Converterはpublic / sealedでシンプルに(トリミングや型解決の事故を減らす)
- 失敗時は例外を握りつぶさずログへ(Releaseでも追える)
- 戻り値は対象プロパティの型に合わせる(Colorを要求するならColorを返す)
DetailPageだけ出ないときの現場的チェックリスト
「アプリ全体が落ちる」というより、「あるページへ遷移した瞬間に落ちる」「そのページだけ表示されない」場合は、次の順で当たりを付けると効率が良いです。
| チェック項目 | 確認方法 | よくある原因 |
|---|---|---|
| InitializeComponentで例外が出ていないか | 未処理例外ログ/Debugでそのページだけ起動 | XAML構文、Resource解決、Converter型解決 |
| ResourceDictionaryのキー重複や参照ミス | 当該ページのResourcesを一旦空にして動くか | StaticResourceが見つからない、順序依存 |
| Converterを全コメントアウト | Converter使用箇所を外し、表示できるか | 型不一致、例外、トリミングで型が消える |
| BindingContext/データがnull | ページ生成直後に値をログ出力 | DI設定ミス、遷移引数の欠落 |
| Release時のみの条件分岐 | #if DEBUG を検索 | Debug専用コードに依存している |
今回のように「コンバーターを差し替えたら直った」場合、原因はConverterそのものだけでなく、Converterが依存しているAPI・型・リソース・NuGetの組み合わせであることもあります。だからこそ、ページ単位で最小化して戻す手順が強力です。
環境起因を疑うなら:Visual Studio / Workload / キャッシュを整える
別環境では動くのに自環境だけReleaseで落ちる場合、コードより先に開発環境の整合性を疑う価値があります。特にMAUIはワークロードやSDKの組み合わせが多く、アップデート途中で壊れることもあります。
よく効くメンテ手順
- Visual Studioを最新へ更新(可能なら17.13系以外も含め最新状態へ)
- Visual Studio Installerの修復(Repair)を実行
bin/obj削除 → クリーンビルド- NuGetキャッシュをクリア(依存の取り違えを防ぐ)
- .NET workloadの更新・修復
コマンド例です(必要なものだけでOKです)。
dotnet --info
dotnet workload list
dotnet workload update
dotnet workload repair
dotnet nuget locals all --clear
そして、MAUI関連のNuGet(特にMicrosoft.Maui.Controlsなど)を更新した場合は、必ずクリーンな状態でビルドし直します。古い生成物が残ったままだと、更新したつもりでも症状が変わらないことがあります。
まとめ:AOTだけに固執せず、Release差分を手順で潰す
- 「Debugは動くがReleaseで無言終了」は、AOT以外(トリミング、XAML、Converter、環境)の可能性が高い。
- 今回の実例では、別環境では正常動作し、AOT無効化でも改善せず、最終的にColor Converter差し替えで解消した。
- 再現が難しいほど、まずはログを取れる状態にして、「無言」をやめる。
- 切り分けは、トリミングOFF→AOT OFF→ページ最小化→1つずつ戻すの順が効率的。
- 自環境だけ怪しいなら、Visual Studio更新・修復、Workload更新、キャッシュ掃除を優先する。
Releaseクラッシュは焦るポイントですが、手順さえ組めれば「原因の見える化」→「小さく再現」→「対策の固定化」まで持っていけます。特にConverterやResourceは、ページ単位で切り分けできるので、疑わしいときほど最小構成に戻すのが最短ルートです。

コメント