LoginSignup
56
61

.NET standard2.x時代のMVVMライブラリ

Last updated at Posted at 2021-03-28

Microsoft.Toolkit.Mvvm

(2023年時点では、CommunityToolkit.Mvvm に名称が変更されています)

WindowsCommunityToolkit の中にあるMVVM用ライブラリの紹介Youtubeに以下のようなベンチマークが出ており、
image.png
そのパフォーマンスと省メモリーに興味がわいたので試してみました。
触って2日目ですので軽いご紹介です。

2022年1月13日更新:
GitHub のソースコード移動のためURL更新

2022年4月25日
新バージョン ver8の新機能を別投稿しています。

特徴

MVVM Lightにインスパイヤされた、よりモダンで軽量なライブラリ。同作者も携わっているとのこと。

  • .NET Standard2.0対応
  • .NET , .net Framework , mono , Xamarin , Uno などで動作。フレームワークに依存しない
  • Mvvm部分だけ単独で利用可能(Toolkit全体は不要)。
  • MVVMLightやPrismに比べるとかなり省メモリらしい。
  • INotifyPropertyChanged, ICommand, Messenger, DI(IoC)の4機能を提供。それぞれ独立しているので使いたいものだけ使える。
  • MITライセンス

インストール

nugetで Microsoft.Toolkit.Mvvm CommunityToolkit.MvvmをインストールすればOK。
.NET5以降なら依存関係なし、40kBぐらいのDLLが1つのみでした。

使い方

4つの機能がありますが独立しているのでObservableObjectだけ利用するなど好きなものを使えます。詳しくはサンプルドキュメントソースコードで確認してください。

提供機能 実装(例) Namespace
INotifyPropertyChanged ObservableObject CommunityToolkit.Mvvm.ComponentModel
ICommand RelayCommand CommunityToolkit.Mvvm.Input
Messenger WeakReferenceMessenger CommunityToolkit.Mvvm.Messeging
DI, IoC IoC CommunityToolkit.Mvvm.DependencyInjection

(修正:名前空間はMicrosoft.Toolkit.MvvmからCommunityToolkit.Mvvmに変更になりました)

ObservableObject

INotifyPropertyChanged の実装です。他のMVVMフレームワークでいう BindableBase などと同様で継承して使います。

VM1.cs
//using Microsoft.Toolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;

public class VM1 : ObservableObject
{
    private string _name;
    public string Name
    {
        get => _name;
        set => SetProperty(ref _name, value);
    }
}

また、INofityPropertyChanged を実装していないModelクラスをラップしてPropertyChangedに反応させることもできます。

Person.cs
//ViewModelで使いたいModelクラス
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}
VM1.cs
//ViewModelでラップしてPropertyChangedに反応させる
public class VM1 : ObservableObject
{
    private Person _person;
    public int Age 
    {
        get => _person.Age;
        set => SetProperty(_person.Age, value, _person, (p, newval) => p.Age = newval);
    }
    public string Name 
    {
        get => _person.Name;
        set => SetProperty(_person.Name, value, _person, (p, newval) => p.Name = newval);
        //SetProperty(古値, 新値, 対象オブジェクト, 値更新(古値!=新値)時に実行するcallback);
    }
}

ObservableObjectの派生として、IMessenger 機能を有したObservableRecipient(後述)や
INotifyDataErrorInfo も実装したObservableValidatorもあるのでそれらの機能が必要になったときにVMの継承を変えればMessengerやValidationが使えるようになります。

RelayCommand

ICommandの実装です。使い勝手も他MVVMライブラリでいうDelegateCommand等とほぼ同様。

VM1.cs
//using Microsoft.Toolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Input;

public class VM1 : ObservableObject
{
    public ICommand ClickCommand { get; }

    public VM1()
    {
        ClickCommand = new RelayCommand(Click);
    }

    private void Click() { Name = ""; }
}

CanExecuteCommandParameter にも当然対応。

RelayCommand(Action execute);
RelayCommand(Action execute, Func<bool> canExecute);
RelayCommand(Action<T?> execute);
RelayCommand(Action<T?> execute, Predicate<T?> canExecute);

非同期版もあり Func<Task>を利用できます。非同期版は CancellationToken を受けることも可能です。これでだいぶ使いやすくなりました。

AsyncRelayCommand(Func<Task>)
AsyncRelayCommand(Func<Task>, Func<Boolean>)
AsyncRelayCommand(Func<CancellationToken,Task>)
AsyncRelayCommand(Func<CancellationToken,Task>, Func<Boolean>)
AsyncRelayCommand<T>(Func<T,Task>)
AsyncRelayCommand<T>(Func<T,Task>, Predicate<T>)
AsyncRelayCommand<T>(Func<T,CancellationToken,Task>)
AsyncRelayCommand<T>(Func<T,CancellationToken,Task>, Predicate<T>)	

Messenger

おそらくこのライブラリの一番の特徴はこの Messenger 機能で、MvvmLight と比べるとメモリ利用は100分の1以下、実行速度も5~10倍以上というベンチマーク結果がでています。(紹介Youtubeから)
image.png
弱参照でメモリーを自動管理してくれるWeakReferenceMessengerと強参照版のStrongReferenceMessengerがありますが、今回は弱参照の方を使ってViewとViewModel間のメッセージングをしてみます。

まずは送受信用メッセージとして小さなクラスを作ります。

MyMessage.cs
//using Microsoft.Toolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging;

public class MyMessage : ValueChangedMessage<string>
{
    public MyMessage(string value) : base(value)
    {
    }
}

参照型ならOKなので、stringをそのまま送信しても動きますが今回はライブラリが用意している値変更用クラス ValueChangedMessage<T> を継承しました。(ソース 見るとわかりますが、Value プロパティがあるだけの実質1行のクラスです)

このメッセージをViewとViewModel間で送受信します。送信側は単純です。

//using Microsoft.Toolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging;

//送信したいところで
WeakReferenceMessenger.Default.Send(new MyMessage("Hello"));

受信側は、まずコンストラクタなどで受信コールバックを登録します。これで受信できるようになります。

//using Microsoft.Toolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging;

//受信関数recv()を登録(コンストラクタ等)
WeakReferenceMessenger.Default.Register<MyMessage>(this, recv);

private void recv(object recipient, MyMessage message)
{
    //受信処理
    Console.WriteLine(message.Value); //"Hello"と出力
}

ViewModelで受信する場合は継承元クラスをObservableRecipientにすることで登録処理をすこし省略できます。ObservableObjectを継承しているので PropertyChanged イベントも対応しています。

VM2.cs
//using Microsoft.Toolkit.Mvvm.ComponentModel;
//using Microsoft.Toolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;

public class VM2 :  ObservableRecipient, IRecipient<MyMessage>
{
    public VM2()
    {
        //メッセージ受信を有効
        IsActive = true;
    }

    //受信部:IRecipientインターフェースの実装
    public void Receive(MyMessage message)
    {
        Console.WriteLine(message.Value); //"Hello"と出力
    }
}

いらなくなったらメッセージ購読解除。

WeakReferenceMessenger.Default.Unregister<MyMessage>(this);

強参照版も使い方はほぼ一緒です。
他に用意されているメッセージタイプ等についてはサンプルのドキュメントソースコードをご確認ください。

IoC/DI

Inversion of Control(制御の反転)を実装したDI向けクラス。
ソースコードを見ればわかりますが必要最小限のヘルパーです。そのためIServiceProviderを実装する別のDIライブラリが必要になります。ここでは Microsoft.Extensions.DependencyInjectionを使って自作のILoggerでも登録してみます。

登録するサービスはサンプルとして自作のMyLoggerにしてみます。

ILogger.cs
public interface ILogger
{
    void Log(string text);
}
MyLogger.cs
public class MyLogger : ILogger
{
    public void Log(string text) => Console.WriteLine(text);
}

これをサービスとして登録します。今回はWPFにしたのでapp.xaml.csで登録します。IoC.Defaultというstaticプロパティが用意されているのでそれを使いシングルトンとして登録します。ConfigureServices()に指定する引数 IServceProvider はMicrosoft.Extensions.DependencyInjectionを利用してます。

app.xaml.cs
//using Microsoft.Toolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;

public partial class App : Application
{
    public App()
    {
        this.InitializeComponent();
        Ioc.Default.ConfigureServices(
            new ServiceCollection()
            .AddSingleton<ILogger,MyLogger>()
            .BuildServiceProvider());
    }
}

登録したMyLoggerをViewModelで使ってみます。

VM2.cs
//using Microsoft.Toolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.DependencyInjection;

public void Receive(MyMessage message)
{
    Ioc.Default.GetService<ILogger>().Log(message.Value);
}

参考

ソースコード
https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Mvvm
2022年1月現在、以下に移動しています。
https://github.com/CommunityToolkit/dotnet

サンプルとドキュメント
https://github.com/windows-toolkit/MVVM-Samples/tree/master/docs/mvvm

公式ドキュメント

56
61
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
56
61