【C#】ViewModelの実装をスニペットで軽減する

WPF/UWP などの XAML 系実装で使用する ViewModel は OSS(Livet, Prism ReactiveProperty) などを使用しない場合、INotifyPropertyChanged 周りの実装が冗長で、繰り返しが面倒なので軽減策の紹介したいと思います。

アプローチ方法は以下の通りです。

  • 共通基底クラスを使用
  • プロパティはスニペット化

確認環境

  • VisualStudio2019
  • C# 9.0

Bindableクラス

まず ViewModel の共通基底クラスとして Bindableクラスを以下のように宣言します。

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;

/// <summary>
/// ViewModel の共通基底クラス
/// </summary>
public abstract class Bindable : INotifyPropertyChanged, IDisposable
{
    // INotifyPropertyChanged impl
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// 指定した名称で <see cref="INotifyPropertyChanged.PropertyChanged"/> を呼び出します。
    /// </summary>
    protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = "")
    {
        if (this.PropertyChanged == null)
        {
            return;
        }
        this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    #region IDisposable

    private bool disposedValue;

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                PropertyChanged = null;
            }
            disposedValue = true;
        }
    }

    #endregion
}

スニペットの作成

次に以下のコードを propvm.snippet としてファイルに保存し、以下のメニューからスニペットを VisualStudio にインポートします。

// インポート方法
> 画面上部の ツール > コード スニペット マネージャー
  > [言語] > CSharp を選択 > インポートボタンを押す

スニペットは以下の通りです。

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>Bindable Property</Title>
      <Shortcut>propvm</Shortcut>
      <Description>Property for INotifyPropertyChanged</Description>
      <Author>Takap</Author>
    </Header>
    <Snippet>
      <Declarations>
        <Literal>
          <ID>valueType</ID>
          <Default>int</Default>
          <ToolTip>公開する変数の型で置き換えます。</ToolTip>
        </Literal>
        <Literal>
          <ID>propName</ID>
          <Default>Sample</Default>
          <ToolTip>公開するプロパティの名前で置き換えます。</ToolTip>
        </Literal>
        <Literal>
          <ID>fieldName</ID>
          <Default>sample</Default>
          <ToolTip>内部で使用する変数の名前で置き換えます。</ToolTip>
        </Literal>
      </Declarations>
      <Code Language="csharp"><![CDATA[private $valueType$ _$fieldName$;
public $valueType$ $propName$
{
    get => _$fieldName$;
    set
    {
        if ($valueType$.Equals(_$fieldName$, value))
        {
            _$fieldName$ = value;
            this.RaisePropertyChanged();
        }
    }
}$end$]]></Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

使い方

Bindable が継承されているクラス上で使用する前提です。

インポート後にエディター上で propvm > Tab > Tab と入力すると以下のようにコードが展開されるので必要個所を入力します。

public class MyViewModel : Bindable // Bindable を継承していること
{ 

    // propvm > Tab > Tab で展開されるコード
    
    // 型、フィールド名、プロパティ名の順に入力する
    private int _sample;
    public int Sample
    {
        get => _sample;
        set
        {
            if (int.Equals(_sample, value))
            {
                _sample = value;
                this.RaisePropertyChanged(); // Bindable を継承してないとエラー
            }
        }
    }

以上