前回「C# による Observer パターンの実装 その4 - Expression を使ってプロパティの指定をタイプセーフに」と云う記事で、Observer パターンの C# による実装の第四回として、Expression を用いることで、前々回文字列でプロパティを指定していた部分をタイプセーフにしてみた。
今回は、それに対する補足だ。
前回の Expression による遣り方とは別の方法で更にアプリケーション部をシンプルにしてみたい。
C# 5 の新機能の Caller Info を使った方法だ。
■ 前回のソースコード
先ず前回のフレームワーク部の Observable のソースコードを再掲する。
・フレームワーク部 - Observable (前回)
// C# による Oberver パターンの実装 その4 using System; using System.Collections.Generic; using System.Linq.Expressions; abstract class Observable // 更新を監視される側 { public event Action<string> Update; protected void RaiseUpdate<PropertyType>(Expression<Func<PropertyType>> propertyExpression) { RaiseUpdate(ObjectExtensions.GetMemberName(propertyExpression)); } void RaiseUpdate(string propertyName) { if (Update != null) Update(propertyName); } }
Observable の RaiseUpdate において、Expression で受けることでプロパティ名を動的に取得している。
この部分を C#5.0 の CallerInfo を使う形で書き換えてみよう。
Caller Info 属性の一つである [CallerMemberName] を付けておくと、呼び出し元のメンバー名 (この場合はプロパティ名) がコンパイラーによって渡されてくる。
これを利用してみよう。
・フレームワーク部 - Observable (今回)
// C# による Oberver パターンの実装 その5 using System; using System.Runtime.CompilerServices; // ☆ [CallerMemberName] の為に必要 abstract class Observable // 更新を監視される側 { public event Action<string> Update; protected void RaiseUpdate([CallerMemberName] string propertyName = "") // ☆ Caller Info 属性を利用 { if (Update != null) Update(propertyName); } }
すると、Observable の派生クラスで、プロパティ名を渡さなくて良くなる。
前回の Employee はこうだった。
・アプリケーション部 - Employee と EmployeeView (前回)
// C# による Oberver パターンの実装 その4 using System; // アプリケーション部 // Model class Employee : Observable { int number = 0; string name = string.Empty; public int Number { get { return number; } set { if (value != number) { number = value; RaiseUpdate(() => Number); // ☆ RaiseUpdate がタイプセーフに } } } public string Name { get { return name; } set { if (value != name) { name = value; RaiseUpdate(() => Name); // ☆ RaiseUpdate がタイプセーフに } } } }
これがこうなる。
・アプリケーション部 - Employee と EmployeeView (今回)
// C# による Oberver パターンの実装 その5 using System; // アプリケーション部 // Model class Employee : Observable { int number = 0; string name = string.Empty; public int Number { get { return number; } set { if (value != number) { number = value; RaiseUpdate(); // ☆ RaiseUpdate の中で文字列でプロパティを渡す必要がなくなった } } } public string Name { get { return name; } set { if (value != name) { name = value; RaiseUpdate(); // ☆ RaiseUpdate の中で文字列でプロパティを渡す必要がなくなった } } } }
前回のような動的にプロパティ名を取得する方法と異なり、コンパイラーが渡してくれるので、オーバーヘッドも小さくなる筈だ。
・全体のソースコード (今回)
全体のソースコードはこうなった。
// C# による Oberver パターンの実装 その5 using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Runtime.CompilerServices; // ☆ [CallerMemberName] の為に必要 // フレームワーク部 public static class ObjectExtensions { // オブジェクトの指定された名前のプロパティの値を取得 public static object Eval(this object item, string propertyName) { var propertyInfo = item.GetType().GetProperty(propertyName); return propertyInfo == null ? null : propertyInfo.GetValue(item, null); } // Expression からメンバー名を取得 public static string GetMemberName<ObjectType, MemberType>(this ObjectType @this, Expression<Func<ObjectType, MemberType>> expression) { return ((MemberExpression)expression.Body).Member.Name; } } abstract class Observable // 更新を監視される側 { public event Action<string> Update; protected void RaiseUpdate([CallerMemberName] string propertyName = "") // ☆ Caller Info 属性を利用 { if (Update != null) Update(propertyName); } } abstract class Observer<ObservableType> where ObservableType : Observable // 更新を監視する側 { Dictionary<string, Action<object>> updateExpressions = new Dictionary<string, Action<object>>(); ObservableType dataSource = null; public ObservableType DataSource { set { dataSource = value; value.Update += Update; } } protected void AddUpdateAction<PropertyType>(Expression<Func<ObservableType, PropertyType>> propertyExpression, Action<object> updateAction) { AddUpdateAction(dataSource.GetMemberName(propertyExpression), updateAction); } void AddUpdateAction(string propertyName, Action<object> updateAction) { updateExpressions[propertyName] = updateAction; } void Update(string propertyName) { Action<object> updateAction; if (updateExpressions.TryGetValue(propertyName, out updateAction)) updateAction(dataSource.Eval(propertyName)); } }
// C# による Oberver パターンの実装 その5 using System; // アプリケーション部 // Model class Employee : Observable { int number = 0; string name = string.Empty; public int Number { get { return number; } set { if (value != number) { number = value; RaiseUpdate(); // ☆ RaiseUpdate の中で文字列でプロパティを渡す必要がなくなった } } } public string Name { get { return name; } set { if (value != name) { name = value; RaiseUpdate(); // ☆ RaiseUpdate の中で文字列でプロパティを渡す必要がなくなった } } } } class TextControl // テキスト表示用のUI部品 (ダミー) { public string Text { set { Console.WriteLine("TextControl is updated: {0}", value); } } } class EmployeeView : Observer<Employee> // Employee 用の View { TextControl numberTextControl = new TextControl(); // Number 表示用 TextControl nameTextControl = new TextControl(); // Name 表示用 public EmployeeView() { AddUpdateAction(employee => employee.Number, number => numberTextControl.Text = ((int)number).ToString()); AddUpdateAction(employee => employee.Name, name => nameTextControl.Text = (string)name); } } class Program { static void Main() { var employee = new Employee(); // Model, Observable var employeeView = new EmployeeView(); // View, Observer employeeView.DataSource = employee; // データバインド employee.Number = 100; // Number を変更。employeeView に反映されたかな? employee.Name = "福井太郎"; // Name を変更。employeeView に反映されたかな? } }
・実行結果
勿論、実行結果に変わりはない。
TextControl is updated: 100 TextControl is updated: 福井太郎
■ 今回のまとめと次回予告
今回は、前回のものに対して補足を行った。
Caller Info を使うことで、アプリケーション部でプロパティを指定する箇所が更にシンプルになった。
次回は、DynamicObject を使った全然別のアプローチを試してみたい。
お楽しみに。