.NET MAUI アプリを .NET 9 へ更新した直後、XAML の Binding に「Source 付きはコンパイルされない(XC0025)」警告が出て、さらに最適化を有効にすると今度は x:Reference/RelativeSource 周りでエラーやコマンド不発が起きる──。この状況を「何が仕様で、どこを直せば安全に速くなるのか」という観点で、原因・対処・切り分けまでまとめます。
XC0025「Source 付き Binding がコンパイルされない」で起きていること
まず結論から言うと、.NET 9 の .NET MAUI で出る XC0025 は、あなたの XAML が “間違っている” というより、「その Binding は compiled binding(コンパイル済みバインディング)としては扱われない」という状態を知らせる警告です。
該当するのは、たとえば次のように Binding に Source= を明示したケースです。
Source={x:Reference ...}でページ/Shell/コントロールを参照Source={RelativeSource AncestorType=...}で祖先要素を参照- (状況によっては)StaticResource などで “BindingContext ではないオブジェクト” を参照
.NET 9 の既定動作では、これらの Binding は compiled binding にならず、実行時解決(リフレクション/実行時探索)にフォールバックします。その結果として、ビルド時に「この Binding はコンパイルされません」という警告(XC0025)が表示されます。
compiled binding とは何が嬉しいのか(なぜ警告が出るのか)
compiled binding(コンパイル済みバインディング)は、XAML で書いた Binding をビルド時に解析し、型情報を使って高速にアクセスできるようにする仕組みです。体感としては以下が効きます。
- パフォーマンス:実行時の探索・反射を減らし、画面描画や大量セル表示で効くことがある
- 安全性:Binding のパスやプロパティ名のミスを、実行時ではなくビルド時に検出しやすい
- 保守性:リネームや型変更に強くなりやすい(ただし “型が合っている” ことが前提)
一方で compiled binding は、「この Binding が最初に参照するオブジェクトの型」が分からないと生成できません。BindingContext だけを使う通常の Binding なら、要素側の x:DataType から型推論できますが、Source= を明示すると「型推論の前提」が崩れます。
そこで .NET 9 では、Source 付き Binding は既定では compiled binding にせず、警告として “意図通り?” を確認させる方向になっています。
対応方針は2択:警告を許容するか、最適化して潰すか
XC0025 が出たからといって必ずしも即修正が必要とは限りません。プロジェクト規模や画面負荷、CI の設定(警告をエラー扱いにするか)で判断が変わります。
| 方針 | 向いているケース | メリット | 注意点 |
|---|---|---|---|
| 警告は残し、現状維持(既定動作のまま) | 警告件数が少ない/性能ボトルネックでない/まず動作優先 | 変更が最小。既存の Binding の挙動が変わりにくい | ビルドログが汚れる。警告をエラー化している環境だと止まる可能性 |
| 最適化を有効にし、Source 付き Binding も compiled 対象へ | 一覧・テンプレートが多い/描画が重い/品質のために型チェックしたい | 速度・安全性の両面で改善余地。Binding の型ズレを早期発見できる | 型ズレが表面化し、別箇所でエラーやコマンド不発が起きることがある(後述) |
.NET 9 で Source 付き Binding を最適化する手順
csproj に最適化フラグを追加する
Source 付き Binding も compiled binding に寄せたい場合、プロジェクトファイル(.csproj)に次を追加します。
<PropertyGroup>
<MauiEnableXamlCBindingWithSourceCompilation>true</MauiEnableXamlCBindingWithSourceCompilation>
</PropertyGroup>
実務では、いきなり全ビルドで有効化せず、まずは Release のみなど段階的に入れるのも安全です。
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<MauiEnableXamlCBindingWithSourceCompilation>true</MauiEnableXamlCBindingWithSourceCompilation>
</PropertyGroup>
重要:Source を使っている「その Binding 自体」に x:DataType を付ける
ここが最大のポイントです。親要素に x:DataType が付いていても、Source の型が別なら別物として扱われます。Source 付き Binding を compiled binding にするには、その Binding が参照する Source の型に合う x:DataTypeを明示する必要があります。
見落としやすい点を整理すると、次の通りです。
- 親の
x:DataTypeは「BindingContext 経由の Binding」を強くするもの Source=...は「BindingContext 以外の別オブジェクト」を参照するため、別の型情報が必要- 型が合っていないと、警告が消えない/フォールバックする/(最適化後は)エラーや不発が起きる
実例:Shell 自身を x:Reference で CommandParameter に渡す(XC0025 対応)
質問で挙がっている典型例がこちらです。
最適化前(警告の対象になりやすい)
CommandParameter="{Binding Source={x:Reference shelly}}"
この Binding は “Source が shelly(Shell インスタンス)” です。したがって、compiled binding にしたいなら x:DataType は Shell を指定します。
最適化後(Source の型に合わせて x:DataType を付ける)
CommandParameter="{Binding Source={x:Reference shelly}, x:DataType=Shell}"
ポイントは、要素側ではなく Binding の中に x:DataType を書くことです。これにより「この Binding は Shell をソースにする」と XamlC が理解でき、compiled binding の対象にできます。
パターン別:x:Reference/RelativeSource/self 参照の “直し方” 早見表
Source 付き Binding で遭遇しやすいパターンを、どの型に x:DataType を合わせるべきかで整理します。
| やりたいこと | よくある書き方 | x:DataType に指定する型 | 補足 |
|---|---|---|---|
| Shell/Page/Control を参照したい | Source={x:Reference foo} | 参照先(foo)の実型 | 親の x:DataType ではなく “参照先の型” を使う |
| 祖先要素(AncestorType)を参照したい | Source={RelativeSource AncestorType=...} | AncestorType に指定した型 | 視覚ツリー上の祖先に存在する必要がある |
| カスタムコントロール内で自分(ルート)を参照したい | Source={x:Reference root} | そのカスタムコントロールの型 | BindbleProperty への参照はこの形が安定 |
| テンプレート内から “外側の画面” を参照したい | RelativeSource AncestorType=Page など | 参照したい外側要素の型 | BindingContext 経由だと object で詰まりやすいので工夫が必要(後述) |
x:Reference を使う Binding の具体例(ページ参照・コントロール参照)
ページを参照してプロパティを読む
たとえば “ページに置いたスイッチの状態を、別の要素から参照したい” といったケースです。
<ContentPage
...
x:Name="page">
<Switch x:Name="debugSwitch" />
<Label
Text="デバッグモード"
IsVisible="{Binding Source={x:Reference debugSwitch}, Path=IsToggled, x:DataType=Switch}" />
</ContentPage>
この例では Source が debugSwitch(Switch)なので、x:DataType=Switch が自然に一致します。
カスタムコントロール内で self 参照して BindableProperty を使う
コントロール内部の子要素から、コントロール自身の BindableProperty を参照したい場合は、BindingContext を上書きせず Source 参照で繋ぐのが事故が少ないです。
<ContentView
x:Class="MyApp.Controls.TitleBanner"
x:Name="root">
<Grid>
<Label
Text="{Binding Source={x:Reference root}, Path=Title, x:DataType=controls:TitleBanner}" />
<Label
Text="{Binding Source={x:Reference root}, Path=Subtitle, x:DataType=controls:TitleBanner}" />
</Grid>
</ContentView>
Source が root(TitleBanner)なので、x:DataType=controls:TitleBanner が必須です。ここがズレると、最適化有効時にエラー化したり、値が出ない(null になる)といった “不発” が起きやすくなります。
RelativeSource(AncestorType)を使う Binding の具体例
DataTemplate 内から外側を参照したいときや、ページ配下の特定の祖先にあるプロパティを読みたいときに使います。
<Label
Text="{Binding Source={RelativeSource AncestorType={x:Type local:MyPage}}, Path=Title, x:DataType=local:MyPage}" />
このケースでは Source が MyPage になる想定なので、x:DataType=local:MyPage を合わせます。AncestorType と x:DataType を同じ型に揃えるのが基本です。
「親に x:DataType があるのに警告が消えない」理由
よくある誤解が、「ページ(親)に x:DataType を付けたから、配下の Binding は全部 compiled になるはず」というものです。これは BindingContext 経由なら概ね正しいのですが、Source を使った瞬間に話が変わります。
次のような構造を想像してください。
- ページ全体の BindingContext は
MainViewModel - 一部だけ
Source={x:Reference ...}で別オブジェクトを参照
親の x:DataType="vm:MainViewModel" は、あくまで “BindingContext の型” を示します。しかし Source 付き Binding のソースは MainViewModel ではなく、参照先(Shell や Page や Control)です。つまり、型が一致しない以上、親の x:DataType はその Binding には使えません。
このため、Source 付き Binding を compiled binding に寄せたいなら、Binding ごとに Source の型へ合わせた x:DataType を指定する必要があります。
最適化を有効化した後に「別画面でクリック/コマンドが効かない」時の考え方
ここは非常に重要です。XC0025 の解消(=最適化の有効化と x:DataType 追加)までは「ビルド時の最適化/警告」の話ですが、最適化後に起きる クリックやコマンド不発は、別の問題として表面化することがあります。
ただし、発生源として多いのは次の2つです。
- Source 付き Binding の x:DataType 未指定/不一致により、Binding が null を返してコマンドが実行されない
- BindingContext の実体が、x:DataType で宣言した型と一致していない(画面遷移・テンプレート・再利用などで起きやすい)
compiled binding は “型が合っている” ことを前提に速くなります。逆に言えば、型が合っていないと、実行時バインディングのように柔軟に拾ってくれず、結果が null になって静かに不発になりやすい、という面があります。
不発時のチェックリスト(切り分け用)
| 確認ポイント | 見るべき症状 | 典型原因 | 対処 |
|---|---|---|---|
| Command が null になっていないか | タップしても何も起きない/CanExecute が常に false | x:DataType と実際の BindingContext の型ズレ | 実行時の BindingContext を確認し、x:DataType を正しい型へ。動的なら x:DataType を外す/局所的に無効化 |
| Source の参照先が想定通りか | x:Reference が取れていない/null のような挙動 | 名前スコープの違い(Template/ControlTemplate など) | 参照の取り方を RelativeSource に寄せる、または参照先を同一スコープへ移す |
| エラーが別箇所に出ていないか | ビルドエラー/XAML の型エラー | Source の型に対して存在しないプロパティを Path で参照 | Path を修正、もしくは参照先を変える(Source を ViewModel にするなど) |
| DataTemplate 内で “外側のVM” を参照していないか | リスト内ボタンだけ効かない | Item の BindingContext と Page の BindingContext が混在している | Source/Ancestor 参照を整理し、必要なら “型付きプロパティ” を用意(後述) |
DataTemplate から親 ViewModel の Command を呼びたいときの実務的な落とし穴
最適化を有効にすると、リストやテンプレート内の Binding が一気に “型に厳密” になります。特に多いのが、DataTemplate の中は Item が BindingContextなのに、ボタンの Command は “画面(ページ)の ViewModel” を呼びたい、というケースです。
ありがちな書き方は次のようなものです(ここでは概念例)。
Command="{Binding Source={RelativeSource AncestorType={x:Type local:MyPage}}, Path=BindingContext.DeleteCommand}"
しかしここで問題になるのが、BindingContext の型が object であることです。compiled binding は中間の型が object だと “その先のプロパティ” を静的に辿れず、最適化有効時に詰まりやすくなります。
現場で安定しやすい解法:ページに「型付き ViewModel プロパティ」を用意する
ページ側に、型付きで ViewModel を返すプロパティを生やすと、XAML 側の Path が静的に辿れるようになります。
MyPage.xaml.cs(例)
public partial class MyPage : ContentPage
{
public MyPage()
{
InitializeComponent();
}
// 重要:型付きで返す(ここが compiled binding の助けになる)
public MainViewModel ViewModel => (MainViewModel)BindingContext;
}
そして XAML 側は「Page を Source にし、ViewModel を経由して Command へ」アクセスします。
Command="{Binding Source={RelativeSource AncestorType={x:Type local:MyPage}},
Path=ViewModel.DeleteCommand,
x:DataType=local:MyPage}"
CommandParameter="{Binding .}"
この形は、
- Source は MyPage(x:DataType=MyPage で一致)
- Path は MyPage.ViewModel(型付き)→ DeleteCommand(静的に辿れる)
となり、compiled binding と相性が良いです。最適化有効時に「テンプレートのボタンだけ効かない」といったケースで、切り札になりやすいテクニックです。
Source を使わない形に書き換えるという選択肢
必ずしも “全部を Source 付き compiled binding にする” 必要はありません。構造的に Source を使うより、設計を少し変えるほうが、結果として安定するケースもあります。
| 現状 | 書き換え案 | 狙い |
|---|---|---|
| CommandParameter に Shell / Page 自体を渡している | ナビゲーションはサービス化し、VM はサービス経由で遷移 | View を VM に渡さない設計に寄せ、Binding の複雑さを減らす |
| x:Reference で UI 要素を参照し値を読む | 必要な状態を ViewModel に集約し、通常の Binding に戻す | Source を減らし、型ズレの温床を減らす |
| コントロール内で BindingContext を self にしている | BindingContext は外部のまま、Source/RelativeSource でコントロール自身のプロパティだけ参照 | 外部 VM を潰さず、再利用性と事故耐性を上げる |
最適化は強力ですが、コードベース全体の “意図が見える設計” とセットで進めると、移行時の痛みが減ります。
移行をスムーズにする進め方(おすすめ手順)
警告を一気に潰そうとすると、どの修正がどの挙動に影響したか分からなくなりがちです。実務で進めやすい流れを挙げます。
- まずは警告一覧を収集(XC0025 が出ている XAML と行番号を拾う)
- 最適化フラグは段階導入(Release のみ、または検証ブランチのみ)
- Source 付き Binding をパターン分け(x:Reference、RelativeSource、self 参照、テンプレート内など)
- パターンごとに x:DataType を揃える(まず “型の一致” を徹底する)
- コマンド不発が出たら、その画面だけ最小化して切り分け(BindingContext と x:DataType の整合性を最優先で確認)
特に 「コマンドが効かない」は、UI 側のイベントや Gesture の問題ではなく、Command が null/別物になっているだけ、というケースが多いです。compiled binding 化の過程では “型の厳密さ” が上がるため、今まで動いていた曖昧さが消え、結果として不発が発生します。これは不具合というより、型ズレが顕在化したサインとして扱うと解決が早くなります。
まとめ:XC0025 の本質と、.NET 9 での正しい直し方
- XC0025 は「Source を明示した Binding は既定では compiled binding にならない」という .NET 9 の仕様に沿った警告
- 最適化(
MauiEnableXamlCBindingWithSourceCompilation)を有効にするなら、Source を使っている “その Binding” に Source の型へ合う x:DataType を必ず付ける - 最適化後に別画面でクリック/コマンドが効かない場合、元の警告とは別問題に見えても、原因は x:DataType の未指定/不一致、BindingContext の取り違えであることが多い
- テンプレート内で親 VM の Command を呼ぶなど、object 型の壁に当たる場合は、型付き ViewModel プロパティを用意するなど “compiled binding が辿れる形” に整えると安定する

コメント