.NET 8/9 と CommunityToolkit.Mvvm を組み合わせると、[ObservableProperty] を付けたメンバーで MVVMTK0045 警告が突然出て驚くことがあります。さらに公式ドキュメント通りに書き換えると、MVVMTK0041 や MVVMTK0052、CS9248 など別のエラーに発展することも。本記事では、その原因と安全な対処パターンを整理します。
想定している環境とよくある状況
この記事では、次のような環境・状況を想定して解説します。
- .NET 8 / .NET 9 のプロジェクト(WPF / WinUI 3 / MAUI など UI フレームワークは問わない)
CommunityToolkit.Mvvmを利用している(8.3.2 または 8.4.x 系)- ViewModel は
ObservableObjectを継承し、[ObservableProperty]属性を使っている - 一部のフィールドにだけ MVVMTK0045 が出る
- 公式ドキュメントを見て「フィールド → プロパティ」に書き換えたら、MVVMTK0041 / 0043 / 0052 / CS9248 が出てビルドが通らなくなった
まさにこういう感じのコードからスタートしているケースです。
using CommunityToolkit.Mvvm.ComponentModel;
public partial class BaseViewModel : ObservableObject
{
[ObservableProperty]
long balance;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(IsNotBusy))]
bool isBusy;
bool IsNotBusy => !IsBusy;
[ObservableProperty]
long newBalance;
}
この状態で、特定のフィールド(たとえば isBusy や title)だけ MVVMTK0045 が出る、というのが典型的な悩みです。
MVVMTK0045 とは何か?ざっくり整理
まず、MVVMTK0045 が何を訴えているのかを整理します。
公式ドキュメントと 8.4 リリースノートを要約すると、MVVMTK0045 は次のような警告です。
- フィールドに対して
[ObservableProperty]を使うと、WinRT + AOT シナリオ(UWP / WinUI 3 + Native AOT など)で問題になる可能性がある。 - 代わりに「partial プロパティ」を使うことを推奨する。
つまり MVVMTK0045 自体は「いますぐビルドが壊れる」という類のものではなく、
- 将来の AOT / WinRT サポート
- コード解析や CsWinRT との連携
を意識した「構造レベルのベストプラクティス警告」です。
8.4 系では、この警告と連動して「フィールド方式 → partial プロパティ方式」へ移行させるための複数の診断(MVVMTK0041〜0045, 0051, 0052…)が一気に追加されています。
フィールド方式と partial プロパティ方式の違い
| 項目 | フィールド方式(従来) | partial プロパティ方式(8.4〜) |
|---|---|---|
| 宣言場所 | [ObservableProperty] は private フィールドに付ける | [ObservableProperty] は public な partial プロパティに付ける |
| 生成されるコード | フィールドを backing field とする通常のプロパティ | C# の partial プロパティ機能+field キーワードを使った実装が生成される |
| AOT / WinRT との相性 | CsWinRT などがプロパティを正しく解析できないケースがある | CsWinRT がプロパティとして認識しやすく、AOT フレンドリー |
| SDK / 言語バージョン | .NET 6/7/8 標準の C# でも問題なく使える | C# の preview 機能が前提(後述) |
| MVVMTK0045 の有無 | 8.3.2 以前では原則として出ない | 8.4.x ではフィールド方式に対して警告が出る |
MVVMTK0041 / 0043 / 0052 / CS9248 が出る理由
MVVMTK0045 を消そうとして、公式ドキュメント通りにプロパティ形式に書き換えたのに、今度は
- MVVMTK0041
- MVVMTK0043
- MVVMTK0052
- CS9248
といったエラーがドッと出てくる理由は、「C# の partial プロパティ という新機能」と「MVVM Toolkit のソースジェネレーター+アナライザー」が密接に噛み合っているためです。
MVVMTK0041:C# 言語バージョンが足りない
MVVMTK0041 の要点をかみ砕くと、
[ObservableProperty]を partial プロパティに付ける場合、<LangVersion>preview</LangVersion>が必須- 理由:ジェネレーターが生成するコードが、C# の preview 機能(
fieldキーワードなど)に依存している
というエラーです。
つまり、MVVMTK0045 のドキュメントに従って「フィールド → partial プロパティ」に書き換えただけでは不十分で、
<PropertyGroup>
<LangVersion>preview</LangVersion>
</PropertyGroup>
を .csproj(または Directory.Build.props)に追加する必要があります。
MVVMTK0043:プロパティの宣言形が要件を満たしていない
MVVMTK0043 は、
[ObservableProperty]を付けるプロパティは- インスタンス メンバー(static ではない)
- partial 修飾子付き
- get / set の両方を持つ
init専用 setter ではない
という条件を満たしていないと発生します。
たとえば、次のように書いてしまうと NG です。
// NG 例:partial がない & set がない
[ObservableProperty]
public long Balance { get; } // MVVMTK0043
正しくは次のように書きます。
[ObservableProperty]
public partial long Balance { get; set; } // OK
MVVMTK0052 と CS9248:partial プロパティの「定義」と「実装」
MVVMTK0052 と CS9248 は、どちらも「partial プロパティの宣言が C# のルールに合っていない」ときに出るエラーです。
- MVVMTK0052:
[ObservableProperty]が付いているプロパティが、「実装部分を持たない partial 定義」になっていない(つまり、プロパティ本体を書いてしまっている) - CS9248:partial プロパティに「定義部分だけがあって、実装部分が存在しない」
ここが少しややこしいポイントなので、もう少し丁寧に説明します。
partial プロパティは「2 つセット」が前提
C# の partial プロパティは、本来は次のように「宣言を 2 回に分ける」前提の機能です。
// 定義(definition)側
public partial int Value { get; set; }
// 実装(implementation)側
public partial int Value
{
get => field;
set { field = value; }
}
この 2 つが揃って初めてコンパイルが通ります。片方だけだと CS9248(または CS9249 など)が出ます。
MVVM Toolkit では「実装側」をソースジェネレーターが自動生成する
MVVM Toolkit の partial プロパティ対応では、
- 開発者が書くのは「定義側」だけ
- 「実装側」はソースジェネレーターが自動で追加する
という設計になっています。
そのため、開発者が書くコードは 「中身なしの partial プロパティ」 である必要があります。
// OK:定義だけを書く
[ObservableProperty]
public partial long Balance { get; set; }
ここで、もし次のように プロパティ本体を書いてしまうと、
// NG:自分で実装を書いてしまう
[ObservableProperty]
public partial long Balance { get => _balance; set => _balance = value; }
MVVMTK0052 が発生します。「[ObservableProperty] を付けるプロパティは partial 定義専用。実装を書くのはジェネレーターの仕事ですよ」というわけです。
CS9248 が出るパターン
一方 CS9248 は、
- partial プロパティの「定義側」だけがあり、
- ジェネレーターが何らかの理由で「実装側」を生成できていない
ような状況で発生します。
典型的には、
<LangVersion>preview</LangVersion>を指定していないために MVVMTK0041 が出ており、ジェネレーターが partial プロパティをスキップしている- ビルドが中断されて実装側の生成が行われていない
といったケースです。この結果、「定義だけあって実装がない」状態になり、CS9248 が噴出します。
結論:実務的な回避策は 2 通り
ここまでの話を踏まえると、実務で選びやすい回避策は次の 2 パターンに整理できます。
| 方針 | 概要 | メリット | デメリット |
|---|---|---|---|
| A. 安定重視:8.3.2 に戻す | 8.4.x ではなく 8.3.2 を利用し、従来のフィールド方式を続ける | MVVMTK0045 自体が出なくなる LangVersion=preview が不要 既存コードの書き換えが最小限 | 8.4.x の新機能(partial プロパティ、追加アナライザーなど)が使えない 将来 AOT / WinRT 移行時に再度置き換え作業が必要 |
| B. 8.4.x を維持して partial プロパティへ移行 | C# 言語バージョンを preview に上げ、public partial プロパティに属性を付ける | MVVMTK0045 を根本的に解消 AOT / WinRT との相性が向上 新しい C# 構文とアナライザーの恩恵を受けられる | LangVersion=preview を許容する運用ルールが必要 partial プロパティの書き方に慣れる必要がある |
回避策 A:CommunityToolkit.Mvvm を 8.3.2 に戻す
まずは、もっとも手堅い「安定重視」の方法です。
手順
.csprojのPackageReferenceを 8.3.2 に固定します。
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.3.2" />
</ItemGroup>
bin/とobj/を削除して、クリーンビルドします。
これだけで、MVVMTK0045 を含む 8.4 系の新しいアナライザー群はそもそも有効にならないため、警告は消えます。
この方法が向いているケース
- 既に本番稼働している業務システムで、安定性が何より重要な場合
- AOT / WinRT(UWP / WinUI 3 + Native AOT)を使う予定がない、または当面考えなくてよい場合
- チームとして C# preview 機能を避けたいポリシーがある場合
逆に、
- いずれ Native AOT や WinUI 3 への展開を見込んでいる
- 新しい MVVM Toolkit の機能(partial プロパティ・追加アナライザー)を積極的に使いたい
といった場合は、次の「回避策 B」を検討する価値があります。
回避策 B:8.4.x を維持し、partial プロパティ方式に移行する
8.4.x の機能を使い続けたい場合は、C# 側の設定とコードの書き方を少しだけ変える必要があります。
手順 1:LangVersion を preview に設定
まずは .csproj(または Directory.Build.props)に次の設定を追加します。
<PropertyGroup>
<LangVersion>preview</LangVersion>
</PropertyGroup>
これは MVVMTK0041 の公式ドキュメントにも明記されており、partial プロパティで [ObservableProperty] を使うための前提条件です。
さらに、partial プロパティを実際にコンパイルできるようにするには、Visual Studio 2022 17.12 以降 + .NET 9 SDK の組み合わせが推奨されています(MVVMTK0044 のドキュメントより)。
手順 2:クラスとプロパティを partial にする
partial プロパティを使うには、クラス自体も partial 修飾子を付ける必要があります。
using CommunityToolkit.Mvvm.ComponentModel;
public partial class BaseViewModel : ObservableObject
{
[ObservableProperty]
public partial long Balance { get; set; }
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(IsNotBusy))]
public partial bool IsBusy { get; set; }
public bool IsNotBusy => !IsBusy;
[ObservableProperty]
public partial long NewBalance { get; set; }
}
BaseViewModelにpartial- 各プロパティも
public partialで宣言 - プロパティ本体は「実装」を書かず、あくまで定義だけ
この形にして LangVersion=preview を有効化すると、MVVM Toolkit のソースジェネレーターが partial プロパティの実装側を自動生成し、MVVMTK0045 も消えてくれます。
手順 3:nameof と派生プロパティの整合性
[NotifyPropertyChangedFor(nameof(IsNotBusy))] のように nameof を使う場合、そのプロパティ本体は自前で実装する必要があります。
public bool IsNotBusy => !IsBusy; // 自前実装
ここで名前の表記ゆれ(Isbusy など)があると、
- ビルドエラー
- あるいはバインディングが動いているように見えて実際には通知されない
といった地味な不具合を生みます。プロパティ名と nameof の一致は必ず確認してください。
「一部のメンバーだけ MVVMTK0045」が出る理由
実際の報告を見ると、
- 同じクラス内のフィールドのうち、いくつかだけ MVVMTK0045 が出る
- 別のフィールドには出ない
という現象がよくあります。
これは、MVVM Toolkit のアナライザーがフィールドの宣言形や属性の組み合わせを見ながら「どのケースに警告を出すか」を細かく制御しているためです。
たとえば次のような要素の違いによって、解析経路や適用される診断が変わる可能性があります。
- アクセス修飾子(
private/internal/public) NotifyPropertyChangedForなど追加の属性が付いているかどうか- WinRT / CsWinRT を使うプロジェクトかどうか
- ビルド時に WinRT 用のターゲットを有効にしているかどうか
内部実装の細かい条件は公開されていませんが、実務上は次のように捉えるのが安全です。
- MVVMTK0045 が出ているフィールドは「partial プロパティへの移行を強く推奨されている箇所」
- 出ていない箇所も、将来の AOT / WinRT を考えるなら、余裕のあるタイミングで partial プロパティに寄せておくと良い
まず確認しておきたいチェックリスト
原因調査を始める前に、次のチェックを順番に行うと、かなりのケースが解決します。
- CommunityToolkit.Mvvm のバージョンを統一
- ソリューション内の複数プロジェクトで 8.3.2 と 8.4.x が混在していないか確認
- NuGet の「管理」でバージョンを揃える
bin/とobj/を削除してクリーンリビルド- ソースジェネレーター系の問題は、キャッシュが悪さをしていることも多い
using CommunityToolkit.Mvvm.ComponentModel;が通っているか- 誤って別の
ObservableObjectを参照していないか確認
- 誤って別の
- ViewModel クラスが
ObservableObjectを継承し、partialが付いているかpublic partial class xxx : ObservableObjectになっているか確認
- プロパティ名の表記ゆれと
nameofの一致IsbusyvsIsBusyのような微妙な違いも要チェック
- 8.4.x を使うなら LangVersion=preview を設定
<LangVersion>preview</LangVersion>が設定されているか- できれば VS 2022 17.12 以降 + .NET 9 SDK でビルドする
フィールド方式 vs partial プロパティ方式の具体例
最後に、従来のフィールド方式と、8.4 で推奨される partial プロパティ方式を、具体的なコードで比較してみます。
従来:フィールド方式(8.3.2 までの基本パターン)
public partial class SampleViewModel : ObservableObject
{
[ObservableProperty]
private long balance;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(IsNotBusy))]
private bool isBusy;
public bool IsNotBusy => !IsBusy;
}
この場合、ソースジェネレーターが概ね次のようなコードを生成します(簡略化)。
public partial class SampleViewModel
{
public long Balance
{
get => balance;
set => SetProperty(ref balance, value);
}
public bool IsBusy
{
get => isBusy;
set
{
if (SetProperty(ref isBusy, value))
{
OnPropertyChanged(nameof(IsNotBusy));
}
}
}
}
これはこれで分かりやすく、C# 12 までの世界では安定して動作します。
8.4 以降:partial プロパティ方式
8.4 系で推奨されるのは、次のような partial プロパティ宣言です。
public partial class SampleViewModel : ObservableObject
{
[ObservableProperty]
public partial long Balance { get; set; }
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(IsNotBusy))]
public partial bool IsBusy { get; set; }
public bool IsNotBusy => !IsBusy;
}
このとき、ジェネレーターは C# の field キーワードを使った partial プロパティ実装を内部的に生成します(イメージ)。
public partial class SampleViewModel
{
public partial long Balance
{
get => field;
set => SetProperty(ref field, value);
}
public partial bool IsBusy
{
get => field;
set
{
if (SetProperty(ref field, value))
{
OnPropertyChanged(nameof(IsNotBusy));
}
}
}
}
このように、プロパティの「定義」と「実装」を C# 言語機能レベルで分離することで、
- 他のソースジェネレーターやアナライザーが「プロパティ」として正しく解析しやすくなる
- WinRT / CsWinRT 向けのメタデータ生成も素直になる
というメリットが生まれます。MVVMTK0045 は、まさにこの新しい世界観への移行を促すための警告だと捉えると理解しやすくなります。
エラー別の簡易トラブルシュート早見表
| エラー / 警告 | 典型的な原因 | 主な対処 |
|---|---|---|
| MVVMTK0045 | [ObservableProperty] がフィールドに付いている | 8.3.2 に戻す / LangVersion=preview + partial プロパティに移行 |
| MVVMTK0041 | partial プロパティで [ObservableProperty] を使っているが、LangVersion が preview 未満 | <LangVersion>preview</LangVersion> を指定 |
| MVVMTK0043 | プロパティが partial でない / static / get だけ / set が init 専用など | public partial T Name { get; set; } の形に揃える |
| MVVMTK0052 | [ObservableProperty] を付けた partial プロパティに実装を書いてしまっている | プロパティ本体の実装を削除し、「定義だけ」にする |
| CS9248 | partial プロパティに定義だけあって、実装が存在しない(ジェネレーターが動いていない) | MVVMTK0041 など他のエラーを解消してから再ビルド / LangVersion や SDK を確認 |
まとめ:どちらの道を選んでも OK。大事なのは「一貫性」
MVVMTK0045 に端を発する「partial プロパティ+C# preview」問題は、
- MVVM Toolkit 8.4 系が「未来志向の構文」を積極的に取り入れた
- その結果、C# コンパイラや IDE 側のバージョンとの整合性が重要になった
という文脈の中で起きています。
実務的には、次のどちらを選んでも構いません。
- 安定運用が最優先なら:
- 8.3.2 に戻す
- 従来のフィールド方式を当面継続
- 8.4.x の機能を活用し、将来の AOT / WinRT も見据えたいなら:
- LangVersion=preview を有効化
- partial プロパティ方式へ段階的に移行
どちらにせよ、
- パッケージバージョンの統一
- LangVersion 設定の明示
- クラス/プロパティの
partial付け忘れ防止 nameofを含むプロパティ名の表記ゆれチェック
といった基本を押さえておくことで、MVVMTK0045 まわりのトラブルはかなり抑えられます。
アップデートのたびに挙動が変わる可能性もあるため、MVVM Toolkit のリリースノートと公式ドキュメントは、SDK 更新のタイミングで一度目を通しておくと安心です。

コメント