.NET MAUI(.NET 9)XC0025「Source付きBindingがコンパイルされない」原因とx:DataTypeでの解決策

.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 が常に falsex: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 を潰さず、再利用性と事故耐性を上げる

最適化は強力ですが、コードベース全体の “意図が見える設計” とセットで進めると、移行時の痛みが減ります。

移行をスムーズにする進め方(おすすめ手順)

警告を一気に潰そうとすると、どの修正がどの挙動に影響したか分からなくなりがちです。実務で進めやすい流れを挙げます。

  1. まずは警告一覧を収集(XC0025 が出ている XAML と行番号を拾う)
  2. 最適化フラグは段階導入(Release のみ、または検証ブランチのみ)
  3. Source 付き Binding をパターン分け(x:Reference、RelativeSource、self 参照、テンプレート内など)
  4. パターンごとに x:DataType を揃える(まず “型の一致” を徹底する)
  5. コマンド不発が出たら、その画面だけ最小化して切り分け(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 が辿れる形” に整えると安定する

この記事を書いた人

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

コメント

コメントする

目次