macOS上でVS Codeを使い、.NET 8の.NET MAUIプロジェクトを.NET 9へ移行するときは、SDKとワークロード、VS Code拡張、csprojの更新が一か所でもズレるとハマりがちです。本記事では移行の手順をチェックリスト化し、移行後にnet9.0-maccatalystで「Xcode 16.2(MacCatalyst 18.2 SDK)が必要」と出て止まる典型パターンまで含めて、再現しやすい解決策をまとめます。
まず押さえる全体像(移行で失敗しない順番)
結論から言うと、成功率が高い順番は次のとおりです。
| 段階 | やること | 目的 | 詰まりやすい点 |
|---|---|---|---|
| .NET SDK | .NET 9 SDKを導入(CPUに合う版) | ビルド基盤の統一 | arm64/x64混在、PATHの取り違え |
| MAUIワークロード | install/update/repairで整合性を取る | iOS/MacCatalyst/Androidのツール一式を揃える | 古いmanifestが残る、キャッシュ破損 |
| VS Code拡張 | C# / C# Dev Kitなどを更新 | 言語サーバ・デバッグの不整合を解消 | 拡張の更新が半端、再起動不足 |
| プロジェクト設定 | csprojのTFM・警告コード・参照パッケージを更新 | ターゲットと依存関係の整合 | TargetFrameworksの更新漏れ、NoWarnのプレフィックス変更 |
| クリーン | bin/obj削除、クリーンビルド | キャッシュ由来の不可解なエラー排除 | 古い中間生成物が残って誤判定 |
移行前の健康診断(ここをやると切り分けが速い)
まず「今どのSDKとワークロードで動いているか」を固定します。移行作業中に環境が揺れると、同じ操作をしても結果が変わりやすいです。
| 確認項目 | コマンド | 見たいポイント |
|---|---|---|
| dotnetの場所 | which dotnet | 意図した場所(/usr/local/share/dotnet 等)になっているか |
| SDK一覧 | dotnet --list-sdks | 8系と9系が混在していてもOK。どれが使われているかが重要 |
| 実行中SDKの詳細 | dotnet --info | HostのRID、アーキテクチャ、インストール場所 |
| ワークロード一覧 | dotnet workload list | maui、ios、maccatalyst、android系がどう入っているか |
| XcodeとCLIツール | xcodebuild -version xcode-select -p | 「ビルドに使われるXcode」がどれか(複数入っているとズレやすい) |
Apple Silicon(M1/M2/M3…)なのにx64のSDKを入れてしまう、あるいはRosetta経由でx64のdotnetを実行してしまうと、ワークロードが別腹扱いになって混乱します。まずは「このMacについて」でApple SiliconかIntelかを確認し、SDKも揃えてください。
.NET 9 SDKをインストール(arm64 / x64を間違えない)
最優先は.NET 9 SDKの導入です。MAUIワークロードやVS Code拡張より先に、土台を9へ寄せます。
- Apple Silicon:arm64の.NET 9 SDK
- Intel Mac:x64の.NET 9 SDK
インストール後に、次が9系になっていることを確認します。
dotnet --version
dotnet --info
複数SDKが入っていても問題ありませんが、「プロジェクトを開いたときに9が選ばれる」状態にするのが大事です。チーム開発や将来の再現性を考えるなら、リポジトリ直下にglobal.jsonを置いてSDKを固定すると安定します。
{
"sdk": {
"version": "9.0.1xx",
"rollForward": "latestFeature"
}
}
versionは手元に入っている9系に合わせてください(dotnet --list-sdksで見える値)。
MAUIワークロードを導入・更新(install → update/repairの順が安全)
.NET MAUIは「SDK本体+ワークロード」で成立します。SDKだけ9にしても、ワークロードが8のままだと不可解な失敗が起きます。
基本コマンド(最小セット)
dotnet workload install maui
dotnet workload update
すでにmauiが入っている環境で更新がこじれた場合は、repairが効くことがあります。
dotnet workload repair
dotnet workload list
プロジェクト側に必要なワークロードを自動で揃えるには、プロジェクトディレクトリでrestoreを実行するのも有効です。
dotnet workload restore
「入れたのに直らない」時の掃除(キャッシュ起因を潰す)
ワークロードやNuGetキャッシュが壊れていると、更新したはずなのに古い状態が残ります。安全策として、次の順に試すと切り分けが早いです。
dotnet nuget locals all --clear
rm -rf ~/.nuget/packages
rm -rf bin obj
(~/.nuget/packagesを消すと再取得が走るので時間はかかりますが、環境が壊れているときは近道です。)
VS Code側の更新(拡張の更新漏れが地味に多い)
VS Code本体は自動更新されることが多い一方で、拡張は更新が滞りがちです。.NET/MAUI開発で最低限見直したいのは次の系統です。
- C#(言語サポート)
- C# Dev Kit(プロジェクト・ソリューション体験の強化)
- .NET Install Tool(SDK導入支援系を使っている場合)
更新後はVS Codeを完全終了して再起動してください(ウィンドウを閉じただけでプロセスが残ることがあります)。また、拡張の状態やキャッシュが壊れていると、最終的に「VS Codeの再インストール」で一気に直るケースもあります。移行作業で沼っているなら、早い段階で選択肢に入れてよいです。
csproj更新の要点(net8→net9だけじゃ足りないポイント)
プロジェクト側は「TFM変更」「警告コード」「不要参照の整理」「OSバージョン指定」の4点を押さえると移行が安定します。
TargetFrameworks(TFM)をnet9へ
典型的にはTargetFrameworksをまとめて更新します。例として、複数ターゲットのMAUIプロジェクトなら次のような形です。
<PropertyGroup>
<TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst</TargetFrameworks>
<UseMaui>true</UseMaui>
<SingleProject>true</SingleProject>
</PropertyGroup>
Windowsターゲットを含める場合は、既存構成に合わせて追加します(例:net9.0-windows10.0.19041.0など)。
Microsoft.Maui.Controls.Compatibilityは「使っていなければ削除」
互換コントロール(Compatibility)を使っていないのに参照だけ残っていると、移行時に警告が増えたり、将来の更新で足かせになります。判断の目安は次のとおりです。
| 状況 | 対応 | 確認の仕方 |
|---|---|---|
| 互換レイアウト/互換コントロールを使っていない | 参照を削除 | コード検索でMicrosoft.Maui.Controls.Compatibilityが出ない |
| 古いForms資産を段階移行中 | 残してOK | 互換系APIを使っている箇所が明確にある |
Microsoft.Extensions.Logging.Debugは「必要なときだけ」
.NET 9にしたからといって必ず追加するものではありません。アプリ側でDebugロガーを明示的に使っていなければ放置でOKです。逆に、明示的に追加している場合は、関連パッケージのバージョン整合性だけ確認してください(MAUIテンプレートの標準構成に寄せるのが安全です)。
SupportedOSPlatformVersionなどのOS指定(最小対応を明文化する)
iOS/MacCatalystは「どのOSバージョンからサポートするか」をプロジェクトに書いておくと、API利用のコンパイル警告が読みやすくなり、将来の更新で事故が減ります。すでに指定があるプロジェクトは、移行を機にポリシーを見直すのがおすすめです。
例(値はプロダクト方針に合わせて調整してください):
<PropertyGroup Condition="'$(TargetFramework)'=='net9.0-ios'">
<SupportedOSPlatformVersion>15.0</SupportedOSPlatformVersion>
</PropertyGroup>
15.0
ここで大事なのは「正解の数値」よりも「意図が明確であること」です。移行後に新しいAPIが絡む警告や、ビルド環境(Xcode/SDK)要件が変わったときの判断が速くなります。
警告コードのプレフィックス変更(XFC → XC)
NoWarnやWarningsAsErrorsなどでMAUIのXAMLコンパイラ警告を指定している場合、.NET 9系では警告コードの接頭辞が変わることがあります。設定があるプロジェクトだけ、次を機械的に置換するとスムーズです。
| 項目 | .NET 8まで | .NET 9以降 | やること |
|---|---|---|---|
| MAUI XAMLコンパイラ警告コード | XFCxxxx | XCxxxx | csprojのNoWarn/WarningsAsErrorsを置換 |
そもそも該当設定が無いなら変更不要です。「移行したら急に警告が増えた」の原因がこれ、というパターンが多いです。
移行後のクリーンビルド(bin/obj削除はほぼ必須)
TFMやワークロードを変えた直後は、中間生成物が古いターゲット前提で残りやすいです。移行後の初回は、潔く掃除してからビルドします。
rm -rf bin obj
dotnet restore
dotnet build
それでも怪しい場合は、ソリューション全体のクリーンを挟みます。
dotnet clean
rm -rf bin obj
dotnet build
最大のハマりどころ:net9.0-maccatalystで「Xcode 16.2(MacCatalyst 18.2 SDK)が必要」
ここが今回の本題です。.NET 9(MAUI 9)のiOS/MacCatalyst周りは、特定のXcode(=そこに含まれるApple SDKヘッダ)を前提にしているため、要件を満たさないとビルドが止まります。エラー文に「Xcode 16.2」「MacCatalyst 18.2 SDK」などが出るのは、今入っているXcodeが古く、必要なSDKが見つからない典型例です。
なぜ止まるのか(原因のイメージ)
- .NETのiOS/MacCatalystバインディングは、Apple SDKのヘッダやAPI定義と整合する必要がある
- .NET 9側が新しいSDKを前提に更新されると、古いXcodeではヘッダが足りずコンパイル/生成の段階で止まる
- 結果として「指定バージョンのXcode/SDKが必要」という形でエラーが出る
王道の解決策:Xcodeを要求バージョンへ更新
基本はこれ一択です。更新後に「ビルドに使われるXcode」が新しいものを指しているかを確認します。
xcodebuild -version
xcode-select -p
複数Xcodeが入っている場合、xcode-selectが古い方を指しているだけで要件エラーになることがあります。その場合は、ビルドに使うXcodeを明示します(パスは環境に合わせてください)。
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
さらに、Xcode更新直後は次もやっておくとトラブルが減ります。
- Xcodeを一度起動して、ライセンス同意や追加コンポーネントのインストールを完了する
- Command Line Toolsが有効になっているか確認する
macOSの都合でXcodeを上げられない場合の選択肢
XcodeはmacOSのバージョン制約を受けるため、MacのOSが古いと「Xcodeを上げたいのに上げられない」が発生します。この場合は現実的に次の分岐です。
| 選択肢 | メリット | デメリット | おすすめ度 |
|---|---|---|---|
| macOSを更新してXcodeも更新 | 正攻法で将来も安定 | OS更新の工数、社内制約があることも | 高 |
| .NET 8のまま運用(移行を延期) | 環境を崩さず継続できる | .NET 9の改善や修正が取れない | 中 |
| ビルド用Macを別途用意(新しいmacOS+Xcode) | 手元のMacを変えずに成果物を作れる | 運用コスト、CI整備が必要 | 中〜高 |
| ワークロードを古い組み合わせに固定 | 環境更新なしで通る可能性 | 依存関係の綱渡り、再現性が落ちる | 低(最終手段) |
「どうしても今すぐ.net9へ上げたい」気持ちは分かりますが、MacCatalystはXcode依存が強いので、長期的にはmacOS/Xcodeを上げる(またはビルドマシンを分離する)のが一番トラブルが少ないです。
回避策:リンカーを「Link Framework SDKs Only」に寄せる
エラーの種類や参照状況によっては、リンカー設定を変えて「新しいAPI参照をできるだけ落とす」ことで回避できることがあります。これは根本解決ではありませんが、アップデートの猶予を作る目的では有効です。
プロジェクトファイルで設定する例(ターゲットに合わせて条件を書きます):
<PropertyGroup Condition="'$(TargetFramework)'=='net9.0-maccatalyst'">
<MtouchLink>SdkOnly</MtouchLink>
</PropertyGroup>
注意点として、リンクを弱めるとアプリサイズ増加や最適化低下につながる可能性があります。挙動に影響が出ることもあるため、回避できたとしても「暫定」と割り切って、最終的にはXcode要件を満たす方向に戻すのがおすすめです。
| 設定 | 狙い | 起こり得る影響 | 向いている局面 |
|---|---|---|---|
| SdkOnly | 新API参照をリンクで落としやすくする | アプリサイズ増、未使用コードが残る | Xcode更新までの暫定 |
| None(非推奨寄り) | とにかく落とさない | サイズ・性能面で不利、ストア提出で不利になる場合も | 切り分け用途のみ |
「更新したのにXcode要件エラーが消えない」時のチェックリスト
Xcodeを入れ替えたつもりでも、実際に参照しているのが古い方のまま、というのがよくあります。次を順番に確認してください。
- Xcodeは本当に要求バージョンになっているか(
xcodebuild -version) xcode-select -pが新しいXcodeのDeveloperディレクトリを指しているか- Xcodeを一度起動し、ライセンス同意・追加コンポーネント導入を済ませたか
- VS Codeを完全再起動したか(プロセスが残っていないか)
rm -rf bin obj後に再ビルドしたか- 複数のdotnetが混在していないか(
which dotnet、dotnet --info)
とくに「Xcodeだけ更新したのに変わらない」は、xcode-selectが古いDeveloperを指しているパターンが多いです。ここが揃うと、急にスッと通ることがよくあります。
移行作業をラクにする実務的なコツ(オリジナルの運用視点)
ブランチ運用:移行ブランチは「環境差分」を小さくする
.NET 8→9移行は、ライブラリの更新やXcode要件など「コード以外の差分」が増えます。移行ブランチでは機能追加を止め、更新差分だけに集中するとレビューが通りやすくなります。特に次の3点は、PRで分けると事故が減ります。
- SDK/ワークロード更新(global.json含む)
- csprojのTFM/警告コード/パッケージ整理
- MacCatalyst(Xcode要件)対応
CIでMacCatalystをビルドするなら「ビルド用Xcodeの固定」が効く
MacCatalystはローカル環境差の影響が大きいので、CIを使うなら「ビルドに使うXcodeを固定」する発想が強いです。ローカルで難しい場合でも、CIで再現できる状態を先に作っておくと、切り分けが一気に楽になります。
VS Codeの不調は「拡張」より「キャッシュ」を疑う
移行のタイミングでよくあるのが、拡張の更新が終わっているのにIntelliSenseやRestoreが不安定、という状態です。その場合は以下が効くことが多いです。
- VS Codeを完全終了して再起動
- 拡張の無効化→有効化(C#関連)
- 最終手段としてVS Code再インストール
最終チェック:この状態なら「移行完了」と言ってよい
| チェック | 合格ライン |
|---|---|
| dotnetが9系で動いている | dotnet --versionが9.0.x系 |
| mauiワークロードが整合している | dotnet workload listでmaui関連が揃い、restore/buildが安定 |
| csprojのターゲットがnet9になっている | TargetFrameworksがnet9.*へ更新済み |
| bin/objを消しても通る | クリーン状態からdotnet buildが成功 |
| MacCatalystでXcode要件を満たす | 要求Xcodeへ更新、または暫定回避策を理解した上で運用 |
参考にすると理解が深まる公式情報(タイトルのみ)
- Microsoft Learn:.NET MAUI for .NET 9 の新機能(Xcode要件やサポートポリシーの考え方)
- Microsoft Learn:.NET for iOS の Xcode requirement(要件エラー時の基本方針)
- Microsoft Q&A:MacCatalyst 18.2 SDK(Xcode 16.2)要件エラーの代表例と回避策
移行は「手順自体は単純」ですが、macOS+Xcode+ワークロードの組み合わせで結果が変わります。まずは本記事の順番どおりに環境を揃え、最後にMacCatalystのXcode要件を満たす(満たせないなら暫定策と割り切る)ところまで一気通貫で進めるのが、最短ルートです。

コメント