VS2015 RC時点の情報に基づいて書いています
XAML系のアプリを書くときに必須となるINotifyPropertyChangedインターフェースを実装した上での、変更通知機能を持ったプロパティがあります。こいつの実装がめんどくさい&文字列指定だとダサいみたいな理由から、INotifyPropertyChangedを実装した以下のようなクラスを準備して、こいつを継承して楽をするという手がよく使われています。
publicclass BindableBase : INotifyPropertyChanged { publicevent PropertyChangedEventHandler PropertyChanged; protectedvirtualbool SetProperty<T>(ref T field, T value, [CallerMemberName]string propertyName = null) { if (Equals(field, value)) { returnfalse; } field = value; var h = this.PropertyChanged; if (h != null) { h(this, new PropertyChangedEventArgs(propertyName)); } returntrue; } }
こいつを継承するとプロパティの定義が以下のようにすっきりします。
privateint age; publicint Age { get { returnthis.age; } set { this.SetProperty(refthis.age, value); } }
問題点
INotifyPropertyChangedというインターフェースを実装したいだけなのに1つしか出来ないクラスの継承をしてしまう。ほかのクラスを継承したいと思ってもできなくなります。まぁ実質そんなことは滅多に無いので困りはしないのですが、縛りは少ないほうがいいですよね。
C# 6.0の機能を使った変更通知プロパティ
C# 6.0ではいろんな機能が追加されています。
これらの機能を駆使してプロパティの定義について考えてみます。
プレーンなプロパティ変更通知
まず、今までの書き方でプレーンにプロパティを定義してみます。
publicclass MainPageViewModel : INotifyPropertyChanged { publicevent PropertyChangedEventHandler PropertyChanged; privatestring name; publicstring Name { get { returnthis.name; } set { if (this.name == value) { return; } this.name = value; var h = this.PropertyChanged; if (h != null) { h(this, new PropertyChangedEventArgs("Name")); } } } }
まずNameを文字列で指定するのがいけてないのでnameof演算子を使っていい感じにします。
publicclass MainPageViewModel : INotifyPropertyChanged { publicevent PropertyChangedEventHandler PropertyChanged; privatestring name; publicstring Name { get { returnthis.name; } set { if (this.name == value) { return; } this.name = value; var h = this.PropertyChanged; if (h != null) { h(this, new PropertyChangedEventArgs(nameof(Name))); } } } }
次にnull条件演算子を使ってPropertyChangedの呼び出しのためのif文を除去します。
publicclass MainPageViewModel : INotifyPropertyChanged { publicevent PropertyChangedEventHandler PropertyChanged; privatestring name; publicstring Name { get { returnthis.name; } set { if (this.name == value) { return; } this.name = value; this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name))); } } }
かなりスッキリしました。
PropertyChangedEventArgsインスタンスが大量生成されないようにする
プロパティの変更通知が大量に発生するとPropertyChangedEventArgsのインスタンスが大量に生成されてしまいます。これを防ぐためにstaticなところに避難させます。これはBindableBaseでは出来なかったこと。
publicclass MainPageViewModel : INotifyPropertyChanged { publicevent PropertyChangedEventHandler PropertyChanged; privatestaticreadonly PropertyChangedEventArgs NamePropertyChangedEventArgs = new PropertyChangedEventArgs(nameof(Name)); privatestring name; publicstring Name { get { returnthis.name; } set { if (this.name == value) { return; } this.name = value; this.PropertyChanged?.Invoke(this, NamePropertyChangedEventArgs); } } }
これで理想的な状態になりました。
コードスニペット化
後は、これくらいならコードスニペットを作っておくと捗りそうです。
<?xml version="1.0" encoding="utf-8"?><CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"><CodeSnippet Format="1.0.0"><Header><Title>propn</Title><Shortcut>propn</Shortcut><Description>プロパティとバッキング フィールド用のコード スニペット</Description><Author>okazuki</Author><SnippetTypes><SnippetType>Expansion</SnippetType></SnippetTypes></Header><Snippet><Declarations><Literal><ID>type</ID><ToolTip>プロパティ型</ToolTip><Default>int</Default></Literal><Literal><ID>property</ID><ToolTip>プロパティ名</ToolTip><Default>MyProperty</Default></Literal><Literal><ID>field</ID><ToolTip>このプロパティのバッキング変数</ToolTip><Default>myVar</Default></Literal></Declarations><Code Language="csharp"><![CDATA[private static readonly PropertyChangedEventArgs $property$PropertyChangedEventArgs = new PropertyChangedEventArgs(nameof($property$)); private $type$ $field$; public $type$ $property$ { get { return this.$field$;} set { if (this.$field$ == value) { return; } this.$field$ = value; this.PropertyChanged?.Invoke(this, $property$PropertyChangedEventArgs); } } $end$]]></Code></Snippet></CodeSnippet></CodeSnippets>
これをpropn.snippetという名前で保存して、コードスニペットマネージャーからインポートすればpropnで先ほどのプロパティの定義が生成されます。