.NET 9 へ移行して .NET MAUI をビルドしようとすると、csproj に <PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" /> が残っていて「Version を明示すべき?」「今どのバージョン?」と迷いがちです。9 系へ揃える考え方と、バージョン確認・更新の具体手順をまとめます。
この記事が解決すること
- .NET 9(.NET MAUI)で Microsoft.Maui.Controls の Version を明示すべきかの判断基準
- $(MauiVersion) が指している実体(=実際に解決される NuGet バージョン)の確認方法
- プロジェクト全体で MAUI のバージョンを揃えて、移行時のハマりを減らす運用パターン
前提:Microsoft.Maui.Controls は「どこから」参照されるのか
Microsoft.Maui.Controls は .NET MAUI の UI(Controls / XAML)を提供する中心パッケージです。ただし、MAUI のアプリ プロジェクトでは、ふつう csproj に明示的な PackageReference を書かなくても動きます。理由は、MAUI アプリは <UseMaui>true</UseMaui> を有効にすることで、MAUI SDK(ワークロード/SDK)が必要な参照をまとめて面倒を見てくれるからです。
一方で、次のようなケースでは Microsoft.Maui.Controls を 明示的に参照している(あるいは参照せざるを得ない)ことがあります。
- MAUI コントロールを含む クラス ライブラリ(カスタムコントロールや共通 UI コンポーネント)を作っている
- ソリューション内で複数の MAUI 関連パッケージを使っており、中央集権的に Version を揃えるために
$(MauiVersion)を導入している - 過去のテンプレートや手元の運用で
PackageReferenceを足しており、移行後も残っている
| プロジェクトの種類 | Microsoft.Maui.Controls を直接参照する必要 | Version 管理のポイント |
|---|---|---|
| MAUI アプリ(単体) | 基本的に不要(UseMaui が参照を供給) | SDK/ワークロード更新が最重要。明示参照しているなら 9 系へ揃える |
| MAUI クラス ライブラリ | 必要になりやすい(ビルド時に参照が必要) | 参照するなら 9.x の具体値で固定し、ソリューション全体で統一 |
| 複数プロジェクト(大規模) | ケースによる | Directory.* でバージョン統一、dotnet list package で実解決を定期監査 |
結論:.NET 9 へ移行するなら Microsoft.Maui.Controls も 9 系へ見直す
質問の核心は「Version を明示すべきか」ですが、移行期に実務で一番トラブルが出るのは「8 系のまま残ってしまう」ことです。プロジェクトが .NET 9 をターゲットにしているのに、Microsoft.Maui.Controls だけが 8.x(あるいは別の MAUI 系パッケージが 8.x)だと、復元(restore)やビルドで不整合が起きやすくなります。
そのため、.NET 9 へ上げるなら次を押さえるのが結論です。
- 明示参照している場合:
Microsoft.Maui.Controlsの Version は 9.x に更新する($(MauiVersion)経由でも同じ) - 明示参照していない場合:MAUI SDK/ワークロードを .NET 9 用に更新し、結果として 9 系が解決される状態を作る
- 複数プロジェクト:「一部だけ 8.x」の混在を作らない(これが移行事故の最大要因)
「Version を明示すべき?」の判断基準
Microsoft.Maui.Controls の Version を csproj に具体値で書くべきかは、プロジェクトの性質と運用(再現性 vs 追従性)で決めるのが現実的です。迷ったら、次の表のどれに近いかで決めるとブレません。
| 方針 | csproj の状態 | メリット | 注意点 | おすすめ度 |
|---|---|---|---|---|
| MAUI SDK/ワークロードに任せる | Microsoft.Maui.Controls の PackageReference を書かない | 余計な固定がなく、SDK 更新で自然に追従できる | CI/開発機の SDK がズレると結果が変わり得る。環境統一が前提 | MAUI アプリでは高い |
| csproj で具体値を固定 | Version="9.0.0"(例)のように固定 | ビルド再現性が高い。CI と開発機の差が出にくい | 更新作業が発生。パッチの取り込みは自分で回す必要 | ライブラリ/大規模では高い |
| プロパティで統一($(MauiVersion)) | Version="$(MauiVersion)" を採用 | 複数プロジェクトを一括更新できる。差分が小さくレビューしやすい | 定義場所が分からないと混乱する。二重定義で意図せず上書きも | 複数プロジェクトで最有力 |
つまり、質問の形(Version="$(MauiVersion)")で運用しているなら、「明示する/しない」よりも「$(MauiVersion) が 9.x を指しているか」が最重要です。逆に、アプリ プロジェクトが単体で、不要な PackageReference が残っているだけなら、いったん消して SDK 側に寄せるのも有効です(ただし、チーム/CI の SDK 統一が前提)。
今使っている Microsoft.Maui.Controls のバージョンを確認する方法
「自分が今どのバージョンを使っているか」を確実に知るには、“csproj に書かれている値”ではなく、“NuGet が実際に解決した値”を見る必要があります。確認手段は大きく 4 つあります。
| 確認方法 | 何が分かる | 強み | 弱み |
|---|---|---|---|
| csproj / props を読む | 「意図した指定」 | 最速。どこで固定しているかが分かる | 依存解決で変わる場合がある(中央管理/推移的参照) |
dotnet list package | 「実際に解決されたバージョン」 | CLI だけで完結。CI でも同じ手順で確認できる | 結果が多いので、絞り込みの工夫が必要 |
| Visual Studio の NuGet 画面 | 直接/推移的の依存関係 | GUI で見やすい。更新もその場でできる | 環境差(VS の設定/SDK)を吸収しないことがある |
| assets / lock ファイル | 解決結果の実体 | 最終結果を追跡できる(差分比較にも強い) | ファイル場所が分かりにくい。内容が巨大 |
CLI だけで確認する:dotnet list package
環境に依存せず確実なのは CLI です。MAUI プロジェクト(またはライブラリ)直下で次を実行します。
dotnet list package
dotnet list package --include-transitive
--include-transitive を付けると、直接参照していないのに引っ張られているバージョンも見えます。Microsoft.Maui.Controls だけを見たい場合は、出力をフィルターします(PowerShell 例)。
dotnet list package --include-transitive | Select-String "Microsoft.Maui.Controls"
ここに表示された値が、あなたの環境で 実際に復元されている Microsoft.Maui.Controls のバージョンです。$(MauiVersion) を使っていても、最終的には具体値として解決されて出てきます。
Visual Studio で確認する:インストール済み/推移的を分けて見る
Visual Studio を使っているなら、NuGet パッケージ マネージャーで Microsoft.Maui.Controls を検索し、インストール済み(Installed)と推移的(Transitive)のどちらに出ているかを見ます。
- Installed に出る:どこかのプロジェクトが直接参照している(csproj に書いている)
- Transitive に出る:SDK や他パッケージ経由で解決されている(csproj に直接は無い)
更新する場合も、同じ画面から 9.x 系を選んでアップデートできます。GUI が使えない環境(軽量 IDE / CI)では、次の「props で固定」運用が効いてきます。
$(MauiVersion) の正体を突き止める(どこで定義されている?)
Version="$(MauiVersion)" で一番多い混乱が、「MauiVersion の定義場所が分からない」です。定義場所はプロジェクト直下とは限らず、ソリューション全体に影響するファイルに置かれていることがあります。
よくある定義場所は次の通りです(上にあるほど頻出)。
Directory.Build.props(リポジトリ直下に置いて全プロジェクトへ適用)Directory.Packages.props(Central Package Management を使う場合)- 個別の
.csproj(プロジェクトだけに適用) - CI のビルド引数(例:
/p:MauiVersion=...)
探し方はシンプルです。リポジトリのルートで MauiVersion を全文検索します。
# Windows(cmd)
findstr /s /i "<MauiVersion>" *.csproj *.props *.targets
# PowerShell(例)
Get-ChildItem -Recurse -Include *.csproj,*.props,*.targets |
Select-String -Pattern "<MauiVersion>" -List
定義が見つかったら、その値が 9.x になっているかを確認します。もし 8.x のままなら、.NET 9 へ移行したタイミングで ここを 9 系へ更新するのが最短ルートです。
.NET 8 から .NET 9(.NET MAUI)へ移行するときの更新手順
「とりあえず TargetFramework を net9.0 に変えた」だけだと、MAUI の依存関係が追従せず躓くことがあります。移行を安定させるための手順を、実務で事故が少ない順に並べます。
| やること | 具体例 | 狙い | ハマりやすい点 |
|---|---|---|---|
| 開発環境の .NET 9 SDK を揃える | dotnet --version で確認 | ビルド結果の差を減らす | 開発者ごとに SDK が違うと再現しない |
| MAUI ワークロードを更新 | dotnet workload update | MAUI の実体を最新に | 更新後は IDE 再起動/再復元が必要なことがある |
| TargetFramework を .NET 9 系へ | net9.0-android など | .NET 9 ランタイムに合わせる | マルチターゲットの書き換え漏れ |
| $(MauiVersion) を 9.x に更新 | <MauiVersion>9.0.0</MauiVersion> | 参照の一括統一 | 二重定義(別ファイルで上書き) |
| Microsoft.Maui.* を 9 系へ揃える | NuGet で更新 / props を更新 | 混在を防ぐ | サードパーティが古い MAUI を要求する場合 |
| 復元キャッシュをクリアして検証 | dotnet nuget locals all --clear | 古いキャッシュの影響排除 | 初回 restore が少し遅くなる |
TargetFramework の例(MAUI アプリ)
MAUI アプリは複数プラットフォームをターゲットにするのが一般的です。移行時は「どれか 1 つだけ net8.0 のまま」が起きやすいので、まとめて見直します。
<PropertyGroup>
<TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst;net9.0-windows10.0.19041.0</TargetFrameworks>
<UseMaui>true</UseMaui>
</PropertyGroup>
Windows のバージョン(windows10.0.19041.0 の部分)はプロジェクト要件に合わせて調整しますが、少なくとも「net9.0-windows…」へ上げて揃えます。
csproj に Version を固定したい場合の書き方(9.x の具体値)
「CI で毎回同じ成果物を作りたい」「チームで SDK の更新タイミングがバラバラ」など、ビルド再現性を重視するなら、Microsoft.Maui.Controls の Version を 9.x の具体値で固定する運用が向きます。ポイントは 2 つです。
- 固定するなら MAUI 関連をまとめて固定する(Controls だけ固定してもズレの原因になる)
- 固定値は ソリューションで 1 箇所に集約する(レビュー/更新が楽)
パターンA:Directory.Build.props で MauiVersion を一括指定
既に $(MauiVersion) を使っているなら、この形が最も移行しやすいです。
<!-- Directory.Build.props(リポジトリ直下) -->
<Project>
<PropertyGroup>
<MauiVersion>9.0.0</MauiVersion>
</PropertyGroup>
</Project>
※上の 9.0.0 は書き方の例です。実運用では、あなたの環境で利用可能な 9.0 系の具体的なパッチ バージョン(例:9.0.1 など)を指定し、更新タイミングをチームで決めると安定します。
個別の csproj では次のように参照します(質問の形そのままです)。
<ItemGroup>
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
</ItemGroup>
更新は Directory.Build.props の 1 箇所で済みます。逆に言うと、ここが 8.x のままだと全体が 8.x に引っ張られます。移行時は真っ先に確認すべき場所です。
パターンB:Central Package Management(Directory.Packages.props)で統一
ソリューション規模が大きい場合は、NuGet の中央管理(Central Package Management)に寄せるとさらに整理できます。Version を 1 ファイルに集約でき、csproj から Version を消せます。
<!-- Directory.Packages.props -->
<Project>
<ItemGroup>
<PackageVersion Include="Microsoft.Maui.Controls" Version="9.0.0" />
</ItemGroup>
</Project>
※ここでも 9.0.0 は例です。9.0 系の具体値に置き換え、更新ルール(例:月次で確認)を決めておくと「いつの間にか古いまま」が起きにくくなります。
この場合、csproj 側は次のように Version を書きません。
<ItemGroup>
<PackageReference Include="Microsoft.Maui.Controls" />
</ItemGroup>
どちらのパターンでも大事なのは「9 系で統一し、実解決を確認する」ことです。
アップデート時にやりがちな失敗と、切り分けのコツ
.NET 9 + .NET MAUI の移行で、現場で多い失敗パターンを先に潰しておくと、原因調査の時間が激減します。
| 症状 | よくある原因 | まずやること | 次の一手 |
|---|---|---|---|
| 復元/ビルドで互換性エラー(NU1202 など) | MAUI パッケージが 8.x のまま混在 | dotnet list package --include-transitive で 8.x を探す | $(MauiVersion) と各 PackageReference を 9.x へ統一 |
| 開発者によってビルド結果が違う | SDK/ワークロードのバージョン差 | dotnet --info と dotnet workload list を共有 | global.json で SDK を固定、更新ルールを決める |
| Visual Studio では動くが CLI で失敗する | VS 側が別の SDK/Workload を使っている | CLI と VS の使用 SDK を合わせる | VS の更新、ワークロード再インストール/更新 |
| 推移的依存で古い MAUI が残る | サードパーティ パッケージが古い依存を要求 | 該当パッケージを最新へ更新 | どうしても必要なら代替/分離、または一時的にバージョン統一で上書き |
| 更新したのに直らない | NuGet キャッシュ/obj/bin が古い | dotnet clean + キャッシュクリア | ソリューションのクリーン、再起動、再 restore |
運用の現実解:どれくらい「固定」すべきか
「具体値で固定するかどうか」は、正解が 1 つではありません。現場で安定しやすい落としどころは、次のように使い分けることです。
- 個人開発・小規模:基本は MAUI SDK/ワークロードに寄せる(不要な固定をしない)
- チーム開発・CI あり:
global.jsonで SDK を固定し、$(MauiVersion)など 1 箇所で 9.x の具体値を管理 - 長期運用:月次/四半期などで「更新日」を決めてパッチを取り込み、
dotnet list packageで差分をレビューする
特に $(MauiVersion) を採用するなら、確認手順もセットで覚えるのがコツです。定義の場所(Directory.Build.props など)と、実解決の確認(dotnet list package)の 2 つを押さえておけば、移行後に「気付いたら 8.x に戻っていた」「開発者によって違う」といった事故を防げます。
まとめ
.NET 9 へ移行するなら、Microsoft.Maui.Controls を含む MAUI 関連は 9 系で統一するのが第一です。Version を csproj に明示するかどうかは運用次第ですが、$(MauiVersion) を使っているなら 定義場所の特定と、dotnet list package で実解決を確認する流れを作ると、移行の迷いとトラブルが一気に減ります。

コメント