はじめに

この記事は、そこそこ攻撃的な内容になります。不快に思ったなら、その時点でブラウザバックをしてください。

MVVM とは?

MVVMは言うまでもなく Model - View - ViewModel を利用した設計手法で、WPFのみならず .NET MAUI などでも利用されます。

そこで、MVVMを活用するにはどうすればいいのか、という点を中心に語りたいと思います。

まず、論外なのは ViewModel と Model を相互参照するパターンです。

View は ViewModel を知っているが、ViewModel は View を知らない。ViewModel は Model を知っているが、Model は ViewModel を知らない。これが大原則です。

何故かというと、Model とはそれ単体でアプリケーションロジックを全て網羅してるからです。特定の ViewModel に依存してはいけません。 例えば、WPF でアプリケーションを作っている時に、Model は「Modelさえ流用すればコンソールアプリだって作れる」そういう状況が理想です。

…いや、理想っていうか、MVVMってそういうモノなんですけどね?

故に、Model が ViewModel を参照しているサイト…正直私も参照したことがありますが、あの人はおそらく MVVM の根本を分かっていないでしょう。

Model から ViewModel に何かを伝えるためには?

Model は ViewModel を知らないのが大原則、ではどうすればいいのか?一応の答えは「イベント通知」になります。 ただし、ある程度の規模になると面倒で複雑で、正直やっていられません。私もサンプルコードを提示できません。

ただし、PrismのEvent AggregatorやCommunityToolkit.MVVMのMessengerといったメッセージングを使えるなら、話はグッと楽になります。

Model から ViewModel にメッセージを飛ばせば良いのです。 メッセージングを利用することで疎結合が守られ、ユニットテストも容易になり、さらにViewModelに依存しない構成を作れます。

さて、ここで読者の皆さんは何か気づきませんか?思い当たる節がなければそれに超したことはありませんが…

Model で ObservableCollection を使うのは基本的に誤りです!ObservableCollection は ViewModel から View に更新通知するコレクションですから。

なので、私も「ObservalbeCollectionAddRange が使えなくて遅い」として カスタムコントロールだか作っていた記事を読んだことがありますが、これは考え方が根本的に間違っています。

Model では ListHashSet など、C# の通常コレクションを利用して、メッセージングで追加するコレクションごと ViewModel に送信します。 そして、ViewModel 側では foreachAdd を利用して追加します…これで遅いというなら、ChatGPT提案による、以下のようなコードで変更通知を一時無効にすればいいでしょう。

// ViewModelでメッセージを受け取り
Messenger.Default.Register<DataUpdateMessage>(this, message =>
{
    // 通知を無効にして一括でデータを追加
    var suppressNotifications = MyObservableCollection as INotifyCollectionChanged;
    if (suppressNotifications != null)
    {
        suppressNotifications.CollectionChanged -= OnCollectionChanged;
    }

    // データを一括追加
    foreach (var item in message.DataCollection)
    {
        MyObservableCollection.Add(item);
    }

    // 通知を再度有効化
    if (suppressNotifications != null)
    {
        suppressNotifications.CollectionChanged += OnCollectionChanged;
        OnCollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }
});

そう、コレクションの変更通知を一度止めて一気に追加、そして追加終了後に変更通知を再度有効化、Reset で一括更新という流れですね。

変に自作カスタムコントロールを作らず、標準のコントロールを使い、自作カスタムコントロールと同等の速度を得る手法がこうなります。 自作カスタムコントロールはメンテナンスコストや拡張性を犠牲にする可能性が高いです、たかが ObservableCollection ごときで使うものじゃありません!

これでも遅いなら、仮想化などの手法も考えなければなりませんが、それ以前に「設計を見直しましょう」

おわりに

基本的に、小規模~中規模の開発なら CommunityToolkit.Mvvm がお薦めです、個人開発なら本当にお薦めですね。そうもいかない場合、たとえば仕事で Prism を使わなければならない状況もあるでしょう。

だけど、私の言っていることは CommunityToolkit.Mvvm でも Prism でも十分実現可能…いえ、そのためのライブラリと言っても過言ではないでしょう。 メッセージングは、小規模開発で利用するだけでも抜群に使いやすいですよ。

改めて言います「Mocel が ViewModel を知っていてはいけない」「ObservableCollection はそんなに遅くないけど、遅いなら設計見直しも視野にいれよう」

そして…私は MVVM のこの形に必ずしも準拠する必要はない、と考えています。 例えば Model に相当する部分が極端に薄い、WPF などを想定したアプリなら Model に相当する部分を ViewModel に入れる事で見通しがよくなることもあるでしょう。

なにより、拙作 RegexTamer.NET ではまさにそういう作りにしています。

極めて小規模かつ、データの保持が必要無いケースでは、ViewModel さえ使わずに済ませていいでしょう。

「MVVM 教条主義に陥らない」ということ、そして「保守性を高めるためにMVVMを使っている」ということ、これを強く意識すれば、そう大幅に間違えることはないと思います。

皆さんの今後の健闘を祈っています。