INotifyPropertyChanged の実装に便利なクラスとコードスニペット
WPF や Silverlight、Windows 8 や Windows RT の Windows ストア アプリでは、UI の記述に XAML を使うことが多い。
そして、データバインドするために INotifyPropertyChanged をしょっちゅう実装することになる。
これが結構面倒なので、開発者は普通、少しでも楽になるような工夫をしている。
そのような「有り勝ちな工夫」について。
■ 工夫する前のコード
「工夫」する前のコードは、こんな感じだ。
using System.ComponentModel; class MyViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; int id = 0; public int Id { get { return id; } set { if (value != id) { id = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Id")); } } } string name = string.Empty; public string Name { get { return name; } set { if (value != name) { name = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Name")); } } } }
プロパティ毎の記述が、結構煩雑だ。
上記ではプロパティが2つしかないが、プロパティが多くなれば、この繰り返しはそれに比例して増えることになる。
■ ヘルパー クラス
Expression や Caller Info を使ったヘルパー クラスを用意するともう少し記述が楽になる。
※ 参考:
- 「C# による Observer パターンの実装 その4 - Expression を使ってプロパティの指定をタイプセーフに」
- 「C# による Observer パターンの実装 その5 - Caller Info を使ってプロパティの指定をよりシンプルに」
こんな感じのヘルパー クラスだ。
using System; using System.ComponentModel; using System.Linq.Expressions; using System.Runtime.CompilerServices; public static class PropertyChangedEventHandlerExtensions { // Caller Info を使っているので C# 5.0 以降 public static void Raise(this PropertyChangedEventHandler onPropertyChanged, object sender, [CallerMemberName] string propertyName = "") { if (onPropertyChanged != null) onPropertyChanged(sender, new PropertyChangedEventArgs(propertyName)); } public static void Raise<PropertyType>(this PropertyChangedEventHandler onPropertyChanged, object sender, Expression<Func<PropertyType>> propertyExpression) { onPropertyChanged.Raise(sender, propertyExpression.GetMemberName()); } static string GetMemberName<MemberType>(this Expression<Func<MemberType>> expression) { return *1; } }</Description> <Author></Author> <SnippetTypes> <SnippetType>Expansion</SnippetType> </SnippetTypes> </Header> <Snippet> <Declarations> <Literal> <ID>type</ID> <ToolTip>type name</ToolTip> <Default>int</Default> </Literal> <Literal> <ID>field</ID> <ToolTip>field name</ToolTip> <Default>myField</Default> </Literal> <Literal> <ID>property</ID> <ToolTip>property name</ToolTip> <Default>MyProperty</Default> </Literal> </Declarations> <Code Language="csharp"><![CDATA[ $type$ $field$ = default($type$); public $type$ $property$ { get { return $field$; } set { if (!value.Equals($field$)) { $field$ = value; PropertyChanged.Raise(this); } } } $end$]]></Code> </Snippet> </CodeSnippet> </CodeSnippets>
これを、"propin.snippet" のような名前で、テキスト ファイルとして任意の場所に保存する。
次に、Visual Studio で、メニューから 「ツール」 - 「コード スニペット マネージャー」を開く。
「インポート」ボタンを押す。先ほどのテキスト ファイル "propin.snippet" を選択する。
"My Code Snippets" にチェックをいれて、「完了」ボタンを押す。
これでコード スニペットが使えるようになる。
例えば、先程の MyViewModel クラスの Name プロパティの下にカーソルを合わせて、propin とタイプして[TAB]キーを2回押してみよう。
新しいプロパティが簡単に挿入でき、型名、フィールド名、プロパティ名が楽に設定できるだろう。
*1:MemberExpression)expression.Body).Member.Name; } }
これを使うことで、先のコードは「少しだけ」簡潔になる。また、プロパティ名の変更に対して安全になった。
using System.ComponentModel; class MyViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; int id = 0; public int Id { get { return id; } set { if (value != id) { id = value; PropertyChanged.Raise(this); } } } string name = string.Empty; public string Name { get { return name; } set { if (value != name) { name = value; PropertyChanged.Raise(this); } } } }
■ コード スニペット
更に Visual Studio でコード スニペットを利用することで、タイピングの手間を減らすことができる。
このコード スニペットは、例えば次のような XML だ。
<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Title>Property for INotifyPropertyChanged</Title>
<Shortcut>propin</Shortcut>
<Description>Code snippet for property for INotifyPropertyChanged.
Use this class (for C# 5.0 or later):
using System.ComponentModel;
using System.Runtime.CompilerServices;
public static class PropertyChangedEventHandlerExtensions
{
public static void Raise(this PropertyChangedEventHandler onPropertyChanged, object sender, [CallerMemberName] string propertyName = "")
{
if (onPropertyChanged != null)
onPropertyChanged(sender, new PropertyChangedEventArgs(propertyName