MVVMを使って実装しているとデータバインディングのために、ViewModelでのプロパティ通知の実装が煩雑でいやになります。微妙に異なる、色々な記法やヘルパーがあるので、今更ですが、まとめておきます。
ヘルパーなし(.NET 3.0)
実際INotifyPropertyChangedインターフェイスのPropertyChangedを呼び出すだけのコードなのですが、ヘルパーなどを使わない場合は、以下のように実装します:
方法 : INotifyPropertyChanged インターフェイスを実装する – MSDN .NET Framework 3.0
.NET 3.0でのイベントハンドラ
-
public event PropertyChangedEventHandler PropertyChanged;
-
protected virtual void OnPropertyChanged(string name)
- {
- if (PropertyChanged == null) return;
- PropertyChanged(this, new PropertyChangedEventArgs(name));
- }
これを利用して変更通知可能なプロパティを実装するにはMSDNでは、以下のようになります。
.NET 3.0でのプロパティ実装
-
public class DemoCustomer : INotifyPropertyChanged
- {
- public string CompanyName
- {
- get {return this.companyNameValue;}
- set
- {
- if (value != this.companyNameValue)
- {
- this.companyNameValue = value;
- NotifyPropertyChanged("CompanyName");
- }
- }
- }
- }
ヘルパーなし(.NET 4.5)
MSDNの.NET 4.5のサンプルでは以下のように表記が楽になります。
方法 : INotifyPropertyChanged インターフェイスを実装する – MSDN .NET Framework 4.5
.NET 4.5でのイベントハンドラ
-
public event PropertyChangedEventHandler PropertyChanged;
-
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
- {
- if (PropertyChanged != null)
- {
- PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
- }
- }
これを利用して変更通知可能なプロパティを実装するにはMSDNでは、以下のようになります。
.NET 4.5でのプロパティ実装
-
public class DemoCustomer : INotifyPropertyChanged
- {
- public string CompanyName
- {
- get {return this.companyNameValue;}
- set
- {
- if (value != this.companyNameValue)
- {
- this.companyNameValue = value;
- NotifyPropertyChanged();
- }
- }
- }
- }
これは表記が短くなるだけでなく、毎回コピペで実装するこのプロパティ実装で文字列の指定によるタイプミスや変更忘れをなくす効果があります。
また、リファクタリングでプロパティ名を自動的に変更しても大丈夫です。
これでかなり楽になりましたね。
ただ、何をやっているかを理解するにはいいですが、プロパティ変化通知可能なクラスを作るたびにコピーするのは煩雑です。通常は自分でベースクラスを作るか、ヘルパーライブラリを用います。
MVVMLight
最もポピュラーなサードパーティMVVMヘルパーライブラリであるMVVMLightではViewModelBaseというベースクラスが用意されています。メソッド名がNotifyPropertyChanged()からRaiseProperyChanged()に代わりますが、基本的には同じものです。
MVVMLight でのプロパティ実装
-
public class DemoCustomer : ViewModelBase
- {
- private string companyNameValue = String.Empty;
- public string CompanyName
- {
- get {return this.companyNameValue;}
- set
- {
- if (value != this.companyNameValue)
- {
- this.companyNameValue = value;
- RaisePropertyChanged();
- }
- }
- }
- }
プロパティ名は省略した記法が用意されています。ただし、MVVMLightの定義には以下のように、省略できるのはC#5 VB11以降のみと注意書きがあります。
-
protected virtual void RaisePropertyChanged(string propertyName = "A property name must be specified if not using C# 5/VB11");
Prism
Microsoftが提供するMVVMヘルパーライブラリであるPrismでも、当然ながら同様の仕組みがNotificationObjectというベースクラスで用意されています。
Prismではもう少し進んだ記法が用意されています。RaisePropertyChaged()は以下のようにExpression型でプロパティ記述を指定できます。ラムダ式などでクラスを指定した形でプロパティ名を通知できます。
-
protected void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression)
以下のように、ラムダ式でCompanyNameを指定できるので、文字列の表記間違いは回避できます。
Prismでのプロパティ実装
-
public class DemoCustomer : NotificationObject
- {
- private string companyNameValue = String.Empty;
- public string CompanyName
- {
- get {return this.companyNameValue;}
- set
- {
- if (value != this.companyNameValue)
- {
- this.companyNameValue = value;
- RaisePropertyChanged( ( ) = > CompanyName);
- }
- }
- }
だいぶ楽になるとは思いますが、表記上の煩雑さはあまり改善されていませんね。もうちょっと何とかならないかと思っていました。
Common.BindableBase
Windows8ストアアプリの実行環境である、WinRTにはCommon.BindableBaseなるベースクラスが用意されています。
これにはSetPropertyというsetterを簡単に書けるメソッドが用意されており、以下のような表記で済ますことができます。
Common.BindableBaseによるプロパティ実装
-
public class DemoCustomer : BindableBase
- {
- private string companyNameValue = String.Empty;
- public string CompanyName
- {
- get {return this.companyNameValue;}
- set { SetProperty(ref companyNameValue, value);}
- }
- }
これは、かなり楽になりますね。なんで今まで無かったのでしょうか?
ただし、このクラスどうも現在のWindows 8.1のライブラリには存在しないように見えます。
またPortableClassLibraryではCommonネームスペース自体が見えません。
自作BindableBase
上記の便利なプロパティ設定を行うだけのベースクラスは以下のような定義で簡単に実装できます。
わけあって、ヘルパーを使えない場合などよろしいですね。
自作BindableBase
-
abstract public class BindableBase : ICleanup, INotifyPropertyChanged
- {
- protected BindableBase(){}
-
- public event PropertyChangedEventHandler PropertyChanged;
- protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
- {
- var handler = PropertyChanged;
- if (handler != null)
- {
- handler(this, new PropertyChangedEventArgs(propertyName));
- }
- }
-
- protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
- {
- if (object.Equals(storage, value)) return false;
- storage = value;
- this.RaisePropertyChanged(propertyName);
- return true;
- }
- }
記事一覧へ