はじめに
この記事は、そこそこ攻撃的な内容になります。不快に思ったなら、その時点でブラウザバックをしてください。
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 に更新通知するコレクションですから。
なので、私も「ObservalbeCollection
は AddRange
が使えなくて遅い」として
カスタムコントロールだか作っていた記事を読んだことがありますが、これは考え方が根本的に間違っています。
Model では List
や HashSet
など、C# の通常コレクションを利用して、メッセージングで追加するコレクションごと ViewModel に送信します。
そして、ViewModel 側では foreach
と Add
を利用して追加します…これで遅いというなら、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を使っている」ということ、これを強く意識すれば、そう大幅に間違えることはないと思います。
皆さんの今後の健闘を祈っています。