MVVMTK0045 警告の原因と対処法まとめ|CommunityToolkit.Mvvm と .NET 8/9 で発生するエラーを徹底解説

.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 プロパティ」に書き換えただけでは不十分で、

&lt;PropertyGroup&gt;
  &lt;LangVersion&gt;preview&lt;/LangVersion&gt;
&lt;/PropertyGroup&gt;

を .csproj(または Directory.Build.props)に追加する必要があります。

MVVMTK0043:プロパティの宣言形が要件を満たしていない

MVVMTK0043 は、

  • [ObservableProperty] を付けるプロパティは
    • インスタンス メンバー(static ではない)
    • partial 修飾子付き
    • get / set の両方を持つ
    • init 専用 setter ではない

という条件を満たしていないと発生します。

たとえば、次のように書いてしまうと NG です。

// NG 例:partial がない &amp; 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 =&gt; 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 =&gt; _balance; set =&gt; _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 に戻す

まずは、もっとも手堅い「安定重視」の方法です。

手順

  1. .csproj の PackageReference を 8.3.2 に固定します。
&lt;ItemGroup&gt;
  &lt;PackageReference Include="CommunityToolkit.Mvvm" Version="8.3.2" /&gt;
&lt;/ItemGroup&gt;
  1. 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)に次の設定を追加します。

&lt;PropertyGroup&gt;
  &lt;LangVersion&gt;preview&lt;/LangVersion&gt;
&lt;/PropertyGroup&gt;

これは 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 =&gt; !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 =&gt; !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 の一致
    • Isbusy vs IsBusy のような微妙な違いも要チェック
  • 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 =&gt; !IsBusy;
}

この場合、ソースジェネレーターが概ね次のようなコードを生成します(簡略化)。

public partial class SampleViewModel
{
    public long Balance
    {
        get =&gt; balance;
        set =&gt; SetProperty(ref balance, value);
    }

    public bool IsBusy
    {
        get =&gt; 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 =&gt; !IsBusy;
}

このとき、ジェネレーターは C# の field キーワードを使った partial プロパティ実装を内部的に生成します(イメージ)。

public partial class SampleViewModel
{
    public partial long Balance
    {
        get =&gt; field;
        set =&gt; SetProperty(ref field, value);
    }

    public partial bool IsBusy
    {
        get =&gt; field;
        set
        {
            if (SetProperty(ref field, value))
            {
                OnPropertyChanged(nameof(IsNotBusy));
            }
        }
    }
}

このように、プロパティの「定義」と「実装」を C# 言語機能レベルで分離することで、

  • 他のソースジェネレーターやアナライザーが「プロパティ」として正しく解析しやすくなる
  • WinRT / CsWinRT 向けのメタデータ生成も素直になる

というメリットが生まれます。MVVMTK0045 は、まさにこの新しい世界観への移行を促すための警告だと捉えると理解しやすくなります。

エラー別の簡易トラブルシュート早見表

エラー / 警告典型的な原因主な対処
MVVMTK0045[ObservableProperty] がフィールドに付いている8.3.2 に戻す / LangVersion=preview + partial プロパティに移行
MVVMTK0041partial プロパティで [ObservableProperty] を使っているが、LangVersion が preview 未満<LangVersion>preview</LangVersion> を指定
MVVMTK0043プロパティが partial でない / static / get だけ / set が init 専用などpublic partial T Name { get; set; } の形に揃える
MVVMTK0052[ObservableProperty] を付けた partial プロパティに実装を書いてしまっているプロパティ本体の実装を削除し、「定義だけ」にする
CS9248partial プロパティに定義だけあって、実装が存在しない(ジェネレーターが動いていない)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 更新のタイミングで一度目を通しておくと安心です。

この記事を書いた人

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

コメント

コメントする

目次