ネタ元→いけてるINotifyPropertyChangedの実装は、結構遅かった - かずきのBlog@Hatena
sender 用インスタンスを取得しているのが、遅くなる一番の要因でしょうな。
// ConstraintExpressionじゃないと駄目 if (senderExpression == null) throw new ArgumentException(); // 式を評価してsender用のインスタンスを得る var sender = Expression.Lambda(senderExpression).Compile().DynamicInvoke();INotifyPropertyChangedのいけてる実装 - かずきのBlog@Hatena
sender は毎回ラムダ式から取得しなくてもいいんじゃないかな。拡張メソッドの引数で渡しても、まだじゅうぶんイケてる。
using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; namespace PropertyChangedSample { class Program { private const int COUNT = 10000; static void Main(string[] args) { { イケてる実装 p = new イケてる実装(); p.PropertyChanged += (s, e) => { }; Stopwatch sw = Stopwatch.StartNew(); foreach (var i in Enumerable.Range(0, COUNT)) { p.Name = "田中" + i; } sw.Stop(); Console.WriteLine("イケてる実装:{0}ms", sw.ElapsedMilliseconds); } { ちょっとイケてる実装 p = new ちょっとイケてる実装(); p.PropertyChanged += (s, e) => { }; Stopwatch sw = Stopwatch.StartNew(); foreach (var i in Enumerable.Range(0, COUNT)) { p.Name = "田中" + i; } sw.Stop(); Console.WriteLine("ちょっとイケてる実装:{0}ms", sw.ElapsedMilliseconds); } { 普通の実装 p = new 普通の実装(); p.PropertyChanged += (s, e) => { }; Stopwatch sw = Stopwatch.StartNew(); foreach (var i in Enumerable.Range(0, COUNT)) { p.Name = "田中" + i; } sw.Stop(); Console.WriteLine("普通の実装:{0}ms", sw.ElapsedMilliseconds); } Console.ReadLine(); } } public class イケてる実装 : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private string _name; public string Name { get { return _name; } set { if (_name == value) { return; } _name = value; PropertyChanged.Raise(() => Name); } } } public class ちょっとイケてる実装 : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private string _name; public string Name { get { return _name; } set { if (_name == value) { return; } _name = value; PropertyChanged.Raise2(this, () => Name); } } } public class 普通の実装 : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private string _name; public string Name { get { return _name; } set { if (_name == value) { return; } _name = value; OnPropertyChanged("Name"); } } private void OnPropertyChanged(string propertyName) { var handler = PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(propertyName)); } } } public static class PropertyChangedEventHandleExtensions { private readonly static Dictionary<Expression, string> _dic = new Dictionary<Expression, string>(); // イケてる実装 public static void Raise<TResult>(this PropertyChangedEventHandler self, Expression<Func<TResult>> property) { if (self == null) { return; } var memberExp = property.Body as MemberExpression; if (memberExp == null) { throw new ArgumentException(); } var senderExp = memberExp.Expression as ConstantExpression; if (senderExp == null) { throw new ArgumentException(); } var sender = Expression.Lambda(senderExp).Compile().DynamicInvoke(); self(sender, new PropertyChangedEventArgs(memberExp.Member.Name)); } // ちょっとイケてる実装 public static void Raise2<TResult>(this PropertyChangedEventHandler self, object sender, Expression<Func<TResult>> property) { if (self == null) { return; } var memberExp = property.Body as MemberExpression; if (memberExp == null) { throw new ArgumentException(); } self(sender, new PropertyChangedEventArgs(memberExp.Member.Name)); } } }
イケてる実装はネタ元を引用&ちょっと改変。
実行結果は次の通り。
イケてる実装:6009ms ちょっとイケてる実装:140ms 普通の実装:9ms
40倍くらい速くなった。普通の実装よりは15倍ほど遅いけど。でも、妥協できるレベルじゃないかな?
取得したプロパティ名をキャッシュして使いまわせれば、もっと速くなると思うけど、スマートな方法がすぐには閃かなかった…。すごくシンプルな実装になっているから、今のままでいいか。