.NET MAUIをCoreCLR-onlyへ移行する方法|Mono selector削除後の互換性テスト

.NET 11 Preview 6で.NET MAUIをCoreCLR-onlyへ移行する場合、最初に行うべきことは、UseMonoRuntimeなどのMono runtime selector設定をプロジェクトとCI/CDから削除することです。そのうえで、Android、iOS、Mac CatalystのReleaseビルドを作成し、リフレクション、動的コード生成、ネイティブライブラリ、起動性能、XAML Hot Reload、iOS固有の実行経路を優先的に検証します。

.NET 11 Preview 6では、対象プラットフォームでCoreCLRが既定になっただけでなく、唯一のランタイムになりました。Monoへの切り替えは.NET 11内ではできません。Monoが不可欠な問題を解消できない場合、Microsoft公式の退避策はターゲットを.NET 10に戻すことです。(Microsoft for Developers)

なお、単体のBlazor WebAssemblyアプリは今回の変更対象外です。一方、.NET MAUI Blazor HybridはWebAssemblyを使わず、ネイティブの.NETプロセス内で実行されるため、通常の.NET MAUIアプリと同様にCoreCLR-only移行テストの対象になります。(Microsoft for Developers)

目次

.NET 11 Preview 6で何が変わったのか

.NET 11 Preview 4では、Android、iOS、Mac Catalyst向け.NET MAUIアプリの既定ランタイムがCoreCLRに変更されました。ただし、その時点ではUseMonoRuntimeを指定してMonoへ戻す暫定経路が用意されていました。

.NET 11 Preview 6では移行スケジュールが前倒しされ、net11.0-androidnet11.0-iosnet11.0-maccatalystを指定した時点でCoreCLRが使用されます。以前のMono選択用MSBuildプロパティは削除されています。Preview 4時点の記事やサンプルに残っている「問題があればMonoへ戻す」という案内は、Preview 6には適用できません。(Microsoft for Developers)

CoreCLR-only移行の対象を整理する

プロジェクトターゲットまたは実行方式今回の影響
.NET MAUI for Androidnet11.0-androidCoreCLRのみ
.NET MAUI for iOSnet11.0-iosCoreCLRのみ
.NET MAUI for Mac Catalystnet11.0-maccatalystCoreCLRのみ
単体のBlazor WebAssemblyブラウザー上のWebAssembly対象外。引き続きMonoを使用
.NET MAUI Blazor HybridMAUI内のネイティブ.NETプロセス対象。CoreCLRで動作

「Blazorを使っているから対象外」と判断するのは危険です。確認すべきなのはRazorコンポーネントの有無ではなく、ブラウザー上のWebAssemblyで実行しているのか、.NET MAUI Blazor Hybridとしてネイティブプロセスで実行しているのかです。(Microsoft Learn)

Mono runtime selectorを削除する移行手順

.NET 10の比較基準を先に残す

設定を変更する前に、現行の.NET 10版をRelease構成でビルドし、次の情報を保存します。

  • AndroidのAPKまたはAABサイズ
  • iOSのIPAサイズ
  • Mac Catalystの配布成果物サイズ
  • コールドスタート時間
  • ウォームスタート時間
  • 最初の操作可能画面が表示されるまでの時間
  • 起動直後と主要画面表示後のメモリ使用量
  • 主要機能の動作結果
  • 使用端末、OS、アーキテクチャ、ビルド構成

比較基準がないまま.NET 11版だけを確認すると、「以前から遅かったのか」「CoreCLR移行後に遅くなったのか」を切り分けられません。Microsoftも、実機上で.NET 10版と.NET 11 Preview 6版のコールドスタート、ウォームスタート、パッケージサイズを比較するよう案内しています。(Microsoft for Developers)

Preview 6 SDKとMAUI workloadを準備する

インストール済みSDKを確認し、.NET 11 Preview 6 SDKを使用していることを明確にします。

dotnet --list-sdks
dotnet workload install maui

複数の.NET SDKがインストールされている開発環境やCIでは、global.jsonで実際にインストールしたPreview 6 SDKの完全なバージョンを固定しておくと、開発PCとビルドエージェントの差を減らせます。MicrosoftもPreview 6 SDKと.NET MAUI workloadを導入して検証するよう求めています。(Microsoft for Developers)

UseMonoRuntimeを削除する

従来のプロジェクトに次の設定がある場合、削除します。

<PropertyGroup>
  <UseMonoRuntime>true</UseMonoRuntime>
</PropertyGroup>

移行後は、ランタイム選択用の設定を残す必要はありません。

<PropertyGroup>
  <TargetFrameworks>net11.0-android;net11.0-ios;net11.0-maccatalyst</TargetFrameworks>
</PropertyGroup>

実際にサポートしていないプラットフォームを無理に追加する必要はありません。既存プロジェクトがAndroidとiOSだけを対象としているなら、その2つだけを残します。

Microsoft公式のNETSDK1242対処方法では、UseMonoRuntimeを削除するか、falseに設定するよう案内されています。ただし、将来の混乱を防ぐには、不要になったプロパティ自体を削除する方が明確です。(Microsoft Learn)

csproj以外に残った設定も検索する

UseMonoRuntimeは、アプリ本体の.csprojだけに書かれているとは限りません。次の場所も確認します。

  • Directory.Build.props
  • Directory.Build.targets
  • 独自の.props.targets
  • GitHub ActionsやAzure PipelinesなどのYAML
  • ビルドスクリプト
  • dotnet buildに渡している-p:UseMonoRuntime=true
  • 環境別のMSBuildプロパティ

PowerShellでは、次のように検索できます。

Get-ChildItem -Recurse -File -Include *.csproj,*.props,*.targets,*.yml,*.yaml |
  Select-String -Pattern 'UseMonoRuntime'

macOSやLinuxでは、次のように検索します。

grep -RIn \
  --include='*.csproj' \
  --include='*.props' \
  --include='*.targets' \
  --include='*.yml' \
  --include='*.yaml' \
  'UseMonoRuntime' .

ローカルビルドでは成功するのにCIだけNETSDK1242になる場合、CIのコマンドラインや共通MSBuildファイルに旧設定が残っている可能性があります。

binとobjを削除してReleaseビルドする

ランタイムやターゲットフレームワークを変更した後は、以前の生成物を残したまま検証しないことが重要です。各プロジェクトのbinobjを削除してから復元し、対象ごとにReleaseビルドします。

dotnet restore

dotnet build MyApp.csproj \
  -f net11.0-android \
  -c Release

dotnet build MyApp.csproj \
  -f net11.0-ios \
  -c Release

dotnet build MyApp.csproj \
  -f net11.0-maccatalyst \
  -c Release

iOSとMac Catalystは、実運用で使用しているmacOS、Xcode、証明書、プロビジョニングプロファイルを含む環境でも確認します。単なるコンパイル成功ではなく、署名、アーカイブ、インストール、起動まで通す必要があります。

Microsoftの公式チェックリストでも、各ターゲットをReleaseでビルド・発行し、実機でアプリ全体のフローを検証することが推奨されています。(Microsoft for Developers)

NETSDK1242が解消しない場合の判断

NETSDK1242は、.NET 11以降のモバイルターゲットでMonoを選択したときに発生するエラーです。

NETSDK1242: Building projects with the Mono runtime is not supported
in .NET 11.0 and later.

対処は次の2択です。

状況対処
CoreCLRへ移行できるUseMonoRuntimeを削除するかfalseにする
Mono固有の依存を短期間で解消できないnet10.0-androidnet10.0-iosへ戻す

.NET 11 Preview 6でMonoを強制的に使い続ける、サポートされた第3の方法はありません。Mono依存がリリースを止める場合は、.NET 10の保守ブランチを残しつつ、依存パッケージや実装をCoreCLR対応へ置き換えるのが現実的です。(Microsoft Learn)

CoreCLR-only移行で優先すべき互換性テスト項目

すべての画面を均等にテストするより、ランタイム変更の影響を受けやすい経路から確認した方が、問題を早く発見できます。

優先度テスト領域主な確認内容合格基準
P0Releaseビルドと配布Android、iOS、Mac Catalystのビルド、署名、インストール対象環境で起動できる
P0基幹業務フローログイン、画面遷移、データ取得、保存、決済など例外や欠落なく完了する
P0リフレクション・動的コードDIスキャン、シリアライズ、プロキシ、式ツリーReleaseでも対象機能が動く
P0ネイティブ・外部パッケージP/Invoke、AAR、XCFramework、Binding Library実機で対象APIが成功する
P0iOS固有経路実機、署名済みアーカイブ、復帰、コールバックSimulatorだけでなく実機でも成功
P1起動性能とサイズcold、warm、操作可能までの時間、成果物サイズ許容した回帰幅以内
P1XAML Hot ReloadXAML、スタイル、Binding、ResourceDictionary開発作業を阻害する問題を把握できる
P1アプリライフサイクルバックグラウンド、復帰、ディープリンク状態破損やクラッシュがない
P2長時間実行と診断メモリ増加、GC、CPU、通信再接続継続利用で劣化しない

リフレクションと動的コードを重点的に確認する

Microsoftは、CoreCLR-only移行時に、リフレクションや動的コード生成を使うサードパーティーライブラリを特に検証するよう案内しています。(Microsoft for Developers)

コードと依存パッケージから、次のような処理を探します。

  • Assembly.GetTypes
  • Assembly.Load
  • Type.GetType
  • Activator.CreateInstance
  • 実行時のDI自動登録
  • 属性を使った型スキャン
  • Expression.Compile
  • 動的プロキシ生成
  • 実行時のシリアライザー構築
  • 文字列で指定した型やメソッドの呼び出し
  • Mono固有APIやMonoの実装差に依存した処理

動的コードの利用可能状態は、診断ログとして記録できます。

using System.Runtime.CompilerServices;

Console.WriteLine(
    $"Dynamic code supported: {RuntimeFeature.IsDynamicCodeSupported}");

Console.WriteLine(
    $"Dynamic code compiled: {RuntimeFeature.IsDynamicCodeCompiled}");

この値だけで互換性を判断してはいけません。実際に動的プロキシを生成する、対象型をシリアライズする、プラグインをロードするといった機能経路まで実行します。

発生しやすい症状

  • MissingMethodException
  • TypeLoadException
  • PlatformNotSupportedException
  • NotSupportedException
  • DIへ登録されるはずのサービスが見つからない
  • JSONやXMLのシリアライズ結果が空になる
  • 特定画面の初回表示だけ失敗する
  • Debugでは動くがReleaseでは失敗する

Debugだけ成功しても合格とは判断できません。Releaseビルドでは、トリミングや発行時最適化によって、静的に使用を確認できないコードが問題になることがあります。

.NET MAUIのトリミングでは、動的に呼び出されるメンバーが削除される可能性があり、必要に応じてDynamicDependencyDynamicallyAccessedMembersなどで依存関係を明示できます。(Microsoft Learn)

修正は、次の順序で検討します。

  1. 依存パッケージを.NET 11対応版へ更新する
  2. リフレクションによる自動登録を明示的な登録へ変える
  3. シリアライザーやプロキシをソース生成方式へ変える
  4. 動的に参照する型やメンバーを属性で明示する
  5. パッケージ提供元へCoreCLR対応状況を確認する

TrimmerRootAssemblyでアセンブリ全体を保持する方法は、原因の切り分けには役立ちます。ただし、成果物サイズを増やし、動的依存の問題を隠すことがあるため、恒久対策として無条件に追加するのは避けます。

ネイティブライブラリとNuGetパッケージを実機で確認する

.NET MAUIプロジェクトがビルドできても、ネイティブ機能が正常に動くとは限りません。特に、次の依存関係は実機で対象機能を呼び出して確認します。

Androidで確認するもの

  • AARやJARを含むBinding Library
  • ABI別に含まれる.so
  • JNIを使うSDK
  • ネイティブコールバック
  • カメラ、位置情報、Bluetooth、NFC
  • 認証SDKや決済SDK
  • バックグラウンドサービス
  • Push通知SDK

Androidでは、エミュレーターと実機でCPUアーキテクチャが異なることがあります。エミュレーターで成功しても、配布対象のarm64実機でネイティブライブラリが読み込めるとは限りません。

iOSとMac Catalystで確認するもの

  • .framework
  • .xcframework
  • 静的ライブラリ
  • iOS Binding Library
  • Objective-CやSwiftとのコールバック
  • Keychain
  • Web認証のリダイレクト
  • Push通知
  • カメラ、写真、位置情報
  • ファイル選択
  • Entitlementsを必要とする機能

Mac Catalystでは、Apple SiliconとIntel Macの両方を配布対象にしている場合、各アーキテクチャ向けのネイティブアセットが揃っているかも確認します。

P/Invokeで確認するもの

DllImportLibraryImportを使っている場合は、次の点を確認します。

  • ライブラリ名
  • エクスポート関数名
  • 引数と戻り値の型
  • StructLayout
  • 文字列のマーシャリング
  • デリゲートの寿命
  • ネイティブ側から返されるハンドル
  • コールバックがGCで回収されないか

失敗時には、次の例外やネイティブクラッシュが手掛かりになります。

  • DllNotFoundException
  • EntryPointNotFoundException
  • BadImageFormatException
  • TypeInitializationException
  • マネージド例外を伴わないプロセス終了

パッケージの復元成功や画面表示だけでは不十分です。バーコード読み取りSDKなら実際にカメラを起動して読み取りまで行うなど、ネイティブコードへ到達する操作をテストします。

起動時間とパッケージサイズを同一条件で比較する

MicrosoftはPreview 6時点で、iOSとMac CatalystはMonoより概ね高速であり、Androidは起動時間とアプリサイズがMono比10%以内になるとの見通しを示しています。ただし、すべてのアプリで改善することを保証するものではなく、実アプリを計測するよう案内しています。(Microsoft for Developers)

比較条件は固定します。

条件固定すべき内容
端末同じ物理端末
OS同じバージョン
ビルドRelease
アーキテクチャ同じABIまたはRID
署名同じ配布条件
初期データ同じアカウントとデータ量
通信同じネットワーク条件
測定開始・終了点同じイベント
実行回数10~20回程度
集計中央値と遅い側の値を確認

単にプロセスが生成された時間ではなく、ユーザーが最初の操作を行える状態までを測定します。スプラッシュ画面が消えても、初期データ取得中で操作できないなら、起動完了とは扱いません。

測定しておきたい指標

  • コールドスタート
  • ウォームスタート
  • 最初の操作可能画面までの時間
  • 初回画面遷移
  • 初回API呼び出し
  • 初回データベースアクセス
  • AndroidのAPKまたはAABサイズ
  • iOSのIPAサイズ
  • Mac Catalystの配布成果物サイズ
  • 起動後のメモリ使用量
  • 主要操作中のCPU使用率

Microsoftが示した「10%以内」は、個別アプリの合格保証ではありません。ただし、社内基準が未整備なら、まず10%を調査開始ラインにし、超えた場合に計測条件、依存パッケージ、初期化処理、R2Rや発行設定を確認すると判断しやすくなります。

CoreCLR移行後は、モバイルアプリでもdotnet-tracedotnet-countersによる診断が利用できます。起動時間やメモリ使用量が悪化した場合は、体感だけで判断せず、トレースを保存して.NET 10版との差を確認します。(Microsoft for Developers)

XAML Hot Reloadは実行時互換性と分けて判定する

.NET 11 Preview 6では、Visual Studio、Visual Studio Code、dotnet watchでのデバッグとHot Reloadが利用できる状態に近づいています。一方で、XAML Hot Reloadと一部のiOS経路は、Microsoftが引き続き作業中の項目として挙げています。(Microsoft for Developers)

そのため、次の2つを分けて判定します。

  • 完全な再ビルド後にアプリが正常動作するか
  • 編集中の変更をHot Reloadで反映できるか

Hot Reloadだけが失敗し、再ビルド後のReleaseアプリが正常なら、まずランタイム互換性ではなく開発ツール経路の問題として扱います。

XAML Hot Reloadで確認する項目

  • LabelやButtonなどの単純なプロパティ変更
  • Gridやレイアウト構造の変更
  • Styleの変更
  • ResourceDictionaryの変更
  • DataTemplateの変更
  • コンパイル済みBinding
  • x:DataTypeを指定した画面
  • Shellによる画面遷移
  • モーダル画面
  • MAUI Blazor Hybridを含む場合のネイティブXAML部分

C# Hot Reloadが動くことと、XAML Hot Reloadが動くことも別々に記録します。「Hot Reload成功」という1項目だけでは、どの経路が利用可能なのか分かりません。

iOSのdotnet watchに必要な設定

.NET 11の公式ドキュメントでは、現時点のiOSプロジェクトでdotnet watchを使用するには、MtouchLinkNoneにする必要があると案内されています。(Microsoft Learn)

開発用途に限定するなら、次のようにDebug構成だけへ適用すると、Release測定への混入を防ぎやすくなります。

<PropertyGroup
  Condition="'$(TargetFramework)' == 'net11.0-ios'
             and '$(Configuration)' == 'Debug'">
  <MtouchLink>None</MtouchLink>
</PropertyGroup>

この設定を有効にしたDebugビルドを、パッケージサイズや起動性能の比較対象にしてはいけません。Hot Reload用の構成と、配布判定用のRelease構成を分けて管理します。

iOSはSimulatorだけで合格にしない

MicrosoftはPreview 6時点でも「一部のiOS経路」が作業中であると説明しています。iOSでは、Simulatorで動作しただけではCoreCLR-only移行の完了とは判断できません。(Microsoft for Developers)

少なくとも、次の経路を分けて確認します。

実行経路確認内容
iOS Simulator・Debugデバッグ、C# Hot Reload、XAML Hot Reload
iOS実機・Debugデバイス接続、権限、ネイティブSDK
iOS実機・Release起動、画面遷移、データ処理、性能
署名済みアーカイブ署名、Entitlements、インストール
配布相当ビルド配布環境での起動と外部連携
バックグラウンド復帰状態復元、通信再開、画面再描画

特に確認したい操作は次のとおりです。

  • コールドスタート
  • バックグラウンド移行と復帰
  • メモリ警告後の復帰
  • ディープリンク
  • Web認証からのコールバック
  • Push通知からの起動
  • カメラや写真へのアクセス
  • 位置情報
  • ファイルピッカー
  • Keychainへの保存と読み出し
  • ネイティブSDKからの非同期コールバック

「Simulatorでは成功、実機だけ失敗」「Debugでは成功、署名済みReleaseだけ失敗」という差は、ランタイムだけでなく、アーキテクチャ、トリミング、署名、Entitlements、ネイティブアセットの問題を疑う材料になります。

不具合を原因別に切り分ける

症状疑う場所次に行うこと
NETSDK1242が出るMono selectorの残存csproj、props、targets、CI引数を検索
Debugは動くがReleaseで失敗リフレクション、トリミング、動的依存trim警告と例外スタックを確認
特定NuGet機能だけ失敗パッケージ互換性更新版、CoreCLR対応、ネイティブ資産を確認
DllNotFoundExceptionABI、RID、ライブラリ配置成果物内のネイティブファイルを確認
Simulatorだけ成功実機アーキテクチャや権限物理端末でRelease実行
実機だけ成功しない署名、Entitlements、ネイティブSDKアーカイブと端末ログを確認
Hot Reloadだけ失敗IDE、dotnet watch、XAML経路完全再ビルド後の動作と分離
起動時間だけ悪化初期化、R2R、パッケージ、計測条件.NET 10と同条件で再計測しトレース取得
長時間後に遅くなるGC、メモリ、イベント解除漏れcountersとメモリ推移を取得

重要なのは、「CoreCLRで壊れた」と一括りにしないことです。発生条件をDebugかReleaseか、Simulatorか実機か、初回か2回目以降か、特定パッケージ使用時だけか、という単位まで絞ると、修正先を判断しやすくなります。

CI/CDにCoreCLR-only移行の検査を組み込む

移行後に古いMono selectorが再追加されないよう、CIで文字列を検査できます。

if git grep -n "UseMonoRuntime"; then
  echo "UseMonoRuntime is not supported for .NET 11 mobile targets."
  exit 1
fi

ビルドマトリクスでは、実際に配布するターゲットをすべてRelease構成で検証します。

  • net11.0-android
  • net11.0-ios
  • net11.0-maccatalyst

Apple向けは、通常のコンパイルだけでなく、既存の署名・アーカイブ工程も通します。ネイティブSDKを使うアプリでは、ビルド成功だけをCIの合格条件にせず、実機またはデバイスファーム上で主要機能を呼び出す自動テストや受け入れテストを追加します。

リリース可否の判断基準

CoreCLR-only移行版をリリース候補に進める条件は、次のように定義できます。

  • リポジトリとCIからUseMonoRuntimeが除去されている
  • 配布対象の全TFMでReleaseビルドが成功する
  • 署名、アーカイブ、インストール、初回起動が成功する
  • ログイン、データ取得、保存などのP0フローが完了する
  • リフレクションや動的コードを使う機能をReleaseで確認済み
  • すべてのネイティブSDKを実機で呼び出している
  • 起動時間と成果物サイズが社内許容範囲内
  • XAML Hot Reloadの問題を製品不具合と混同していない
  • .NET 10へ戻す条件と責任者が決まっている

プレビュー版を本番採用するかどうかは、通常の互換性テストとは別に判断します。Preview 6で問題を発見した場合は、.NET 11の正式リリースを待つ、該当パッケージの更新を待つ、.NET 10のリリースブランチを維持するといった選択肢を残します。

問題報告に必要な情報を揃える

再現性の低い「起動が遅くなった」「iOSで動かない」という報告だけでは、原因特定に時間がかかります。Microsoftへ問題を報告する場合は、少なくとも次の情報を含めます。

  • .NET SDKの完全なバージョン
  • .NET MAUI workloadのバージョン
  • ターゲットフレームワーク
  • DebugまたはRelease
  • Runtime IdentifierとCPUアーキテクチャ
  • 端末名とOSバージョン
  • Simulator、エミュレーター、実機の区別
  • .NET 10版と.NET 11版のパッケージサイズ
  • cold、warmの起動時間
  • 例外スタックまたは端末ログ
  • 再現手順
  • 最小構成の再現プロジェクト
  • 使用しているネイティブSDKや関連NuGetパッケージ

Androidの問題はdotnet/android、iOSとMac Catalystの問題はdotnet/maciosで受け付けられています。Microsoftは、アプリの種類、パッケージサイズ、起動時間、可能であれば再現サンプルを添えて報告するよう案内しています。(Microsoft for Developers)

CoreCLR-only移行で最初に実行すること

.NET 11 Preview 6への移行では、Mono selectorを別の設定へ置き換えるのではなく、削除することが出発点です。UseMonoRuntimeをリポジトリ全体とCI/CDから除去し、Android、iOS、Mac CatalystをCoreCLRでReleaseビルドします。

その後は、通常の画面確認より先に、リフレクション、動的コード、ネイティブ依存、実機起動、署名済みiOS経路を検証します。起動性能とパッケージサイズは、保存しておいた.NET 10版と同一条件で比較します。XAML Hot Reloadの問題は、完全再ビルド後の製品動作と分けて判定することが重要です。

Mono依存の問題がリリースを止める場合、.NET 11内でMonoへ戻そうとせず、.NET 10ブランチを維持しながら依存箇所を解消します。この順序で進めれば、CoreCLRそのものの問題、サードパーティーパッケージの問題、トリミングやネイティブアセットの問題、開発ツールの問題を混同せずに移行できます。

この記事を書いた人

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

コメント

コメントする

目次