INotifyPropertyChanged を使用するときにプロパティ名を指定する最良の方法は何ですか?
ほとんどの例では、PropertyChanged イベントの引数としてプロパティ名をハードコーディングしています。私は MethodBase.GetCurrentMethod.Name.Substring(4) を使用することを考えていましたが、リフレクションのオーバーヘッドについて少し不安です。
INotifyPropertyChanged を使用するときにプロパティ名を指定する最良の方法は何ですか?
ほとんどの例では、PropertyChanged イベントの引数としてプロパティ名をハードコーディングしています。私は MethodBase.GetCurrentMethod.Name.Substring(4) を使用することを考えていましたが、リフレクションのオーバーヘッドについて少し不安です。
1 つ忘れないでください。PropertyChanged
イベントは主に、リフレクションを使用して名前付きプロパティの値を取得するコンポーネントによって消費されます。
最も明白な例はデータバインディングです。
プロパティの名前をパラメーターとして渡してイベントを発生させると、このイベントのサブスクライバーは、たとえば(少なくとも のキャッシュを使用する場合は初めて)を呼び出してリフレクションを使用する可能性が高いことPropertyChanged
を知っておく必要があります。 . この最後の呼び出しは、プロパティの getter メソッドの動的な呼び出し (MethodInfo.Invoke) であり、メタ データのみをクエリする よりもコストがかかります。(データ バインディングはTypeDescriptor全体に依存することに注意してください。ただし、既定の実装ではリフレクションが使用されます。)GetProperty
PropertyInfo
GetValue
GetProperty
もちろん、PropertyChanged を起動するときにハードコードのプロパティ名を使用する方が、リフレクションを使用してプロパティの名前を動的に取得するよりも効率的ですが、私見では、考えのバランスをとることが重要です。場合によっては、パフォーマンスのオーバーヘッドがそれほど重要ではなく、厳密に型指定されたイベント起動メカニズムで何らかのメリットが得られることがあります。
これは、パフォーマンスが問題にならない場合に、C# 3.0 で時々使用するものです。
public class Person : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return this.name; }
set
{
this.name = value;
FirePropertyChanged(p => p.Name);
}
}
private void FirePropertyChanged<TValue>(Expression<Func<Person, TValue>> propertySelector)
{
if (PropertyChanged == null)
return;
var memberExpression = propertySelector.Body as MemberExpression;
if (memberExpression == null)
return;
PropertyChanged(this, new PropertyChangedEventArgs(memberExpression.Member.Name));
}
public event PropertyChangedEventHandler PropertyChanged;
}
式ツリーを使用してプロパティの名前を取得し、ラムダ式を次のように使用していることに注意してExpression
ください。
FirePropertyChanged(p => p.Name);
ここでのリフレクション オーバーヘッドは、特に INotifyPropertyChanged が頻繁に呼び出されるため、かなりやり過ぎです。可能であれば、値をハードコーディングすることをお勧めします。
パフォーマンスに関心がない場合は、以下で説明するさまざまなアプローチを検討し、必要なコーディング量が最も少ないものを選択します。明示的な呼び出しの必要性を完全になくすために何かできることがあれば、それが最善です (例: AOP)。
式ツリーの使用に伴うパフォーマンス ヒットは、式ツリーの解決が繰り返されるためです。
次のコードは依然として式ツリーを使用しているため、リファクタリングや難読化に適しているという利点がありますが、実際には、変更通知ごとに PropertyChangedEventArgs オブジェクトを新しくする通常の手法よりも約 40% 高速です (非常に大まかなテスト)。 .
プロパティごとに静的な PropertyChangedEventArgs オブジェクトをキャッシュするため、より高速で、式ツリーのパフォーマンス ヒットを回避できます。
まだ行っていないことが 1 つあります。提供された PropertyChangedEventArgs オブジェクトのプロパティ名が、それが使用されているプロパティと一致することをデバッグ ビルドでチェックするコードを追加するつもりです。現時点では、このコードではまだ開発者が間違ったオブジェクトを提供する可能性があります。
見てみな:
public class Observable<T> : INotifyPropertyChanged
where T : Observable<T>
{
public event PropertyChangedEventHandler PropertyChanged;
protected static PropertyChangedEventArgs CreateArgs(
Expression<Func<T, object>> propertyExpression)
{
var lambda = propertyExpression as LambdaExpression;
MemberExpression memberExpression;
if (lambda.Body is UnaryExpression)
{
var unaryExpression = lambda.Body as UnaryExpression;
memberExpression = unaryExpression.Operand as MemberExpression;
}
else
{
memberExpression = lambda.Body as MemberExpression;
}
var propertyInfo = memberExpression.Member as PropertyInfo;
return new PropertyChangedEventArgs(propertyInfo.Name);
}
protected void NotifyChange(PropertyChangedEventArgs args)
{
if (PropertyChanged != null)
{
PropertyChanged(this, args);
}
}
}
public class Person : Observable<Person>
{
// property change event arg objects
static PropertyChangedEventArgs _firstNameChangeArgs = CreateArgs(x => x.FirstName);
static PropertyChangedEventArgs _lastNameChangeArgs = CreateArgs(x => x.LastName);
string _firstName;
string _lastName;
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
NotifyChange(_firstNameChangeArgs);
}
}
public string LastName
{
get { return _lastName; }
set
{
_lastName = value;
NotifyChange(_lastNameChangeArgs);
}
}
}
ローマン:
「Person」パラメーターさえ必要ないと思います-したがって、以下のような完全に一般的なスニペットで十分です。
private int age;
public int Age
{
get { return age; }
set
{
age = value;
OnPropertyChanged(() => Age);
}
}
private void OnPropertyChanged<T>(Expression<Func<T>> exp)
{
//the cast will always succeed
MemberExpression memberExpression = (MemberExpression) exp.Body;
string propertyName = memberExpression.Member.Name;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
...ただし、デバッグ ビルドでは条件付き検証を使用して文字列パラメーターに固執することを好みます。Josh Smith がこれに関する素敵なサンプルを投稿しました:
INotifyPropertyChanged を実装する基本クラス
乾杯 :) フィリップ
ええ、あなたが提案している機能の使用と単純さはわかりますが、リフレクションによるランニングコストを考慮すると、そうではありません。このシナリオで使用するのは、時間を利用するためにコードスニペットを適切に追加することですすべての Notifyproperty イベントが発生するプロパティの書き込みエラー。
私が考えることができるもう一つの非常に素晴らしい方法は
アスペクトAOPを使用したINotifyPropertyChangedの自動実装
:アスペクト指向プログラミング
codeprojectに関する素晴らしい記事:INotifyPropertyChangedのAOP実装
ハードコードとリフレクションの間で、無関係ではありませんが、私の選択は次のとおりです:notifypropertyweaver。
このVisualStudioパッケージを使用すると、パフォーマンスを失うことなく、リフレクションの利点(保守性、読みやすさなど)を利用できます。
実際には、INotifyPropertyChangedを実装するだけで、コンパイル時にすべての「通知情報」が追加されます。
コードを完全に最適化したい場合も、これは完全にパラメーター化できます。
たとえば、notifypropertyweaverを使用すると、エディターに次のコードが表示されます。
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string GivenNames { get; set; }
public string FamilyName { get; set; }
public string FullName
{
get
{
return string.Format("{0} {1}", GivenNames, FamilyName);
}
}
}
それ以外の :
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string givenNames;
public string GivenNames
{
get { return givenNames; }
set
{
if (value != givenNames)
{
givenNames = value;
OnPropertyChanged("GivenNames");
OnPropertyChanged("FullName");
}
}
}
private string familyName;
public string FamilyName
{
get { return familyName; }
set
{
if (value != familyName)
{
familyName = value;
OnPropertyChanged("FamilyName");
OnPropertyChanged("FullName");
}
}
}
public string FullName
{
get
{
return string.Format("{0} {1}", GivenNames, FamilyName);
}
}
public virtual void OnPropertyChanged(string propertyName)
{
var propertyChanged = PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
フランス語を話す人の場合:Améliorezlarisibilitédevotre code et simplifiez vous la vie avec notifypropertyweaver
リフレクションベースの方法の問題は、それがかなり高価であり、それほど速くないということです。確かに、それははるかに柔軟性があり、リファクタリングに対してもろくありません。
ただし、特に頻繁に呼び出される場合は、パフォーマンスが低下する可能性があります。スタックフレーム方式も(私は信じていますが)CASに問題があります(たとえば、XBAPなどの制限された信頼レベル)。ハードコーディングするのが最善です。
WPFで高速で柔軟なプロパティ通知を探している場合は、解決策があります。DependencyObjectを使用してください:)それが設計された目的です。依存関係を取得したくない場合、またはスレッドアフィニティの問題を心配する場合は、プロパティ名を定数に移動して、ブームになります。あなたの善。
私は実験として一度このようなことをしましたが、メモリからは問題なく動作し、すべてのプロパティ名を文字列にハードコーディングする必要がなくなりました。大量のサーバーアプリケーションを構築している場合、パフォーマンスが問題になる可能性があります。デスクトップでは、おそらく違いに気付くことはありません。
protected void OnPropertyChanged()
{
OnPropertyChanged(PropertyName);
}
protected string PropertyName
{
get
{
MethodBase mb = new StackFrame(1).GetMethod();
string name = mb.Name;
if(mb.Name.IndexOf("get_") > -1)
name = mb.Name.Replace("get_", "");
if(mb.Name.IndexOf("set_") > -1)
name = mb.Name.Replace("set_", "");
return name;
}
}
さらに別のアプローチ: http://www.codeproject.com/Articles/450688/Enhanced-MVVM-Design-w-Type-Safe-View-Models-TVM
さらに、メソッド名の取得がデバッグ ビルドとリリース ビルドで異なる動作をする問題が見つかりました。
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/244d3f24-4cc4-4925-aebe-85f55b39ec92
(私たちが使用していたコードは、あなたが提案した方法を正確に反映したものではありませんでしたが、プロパティ名をハードコーディングすることが最速で最も信頼できるソリューションであると確信しました。)
INotifyPropertyChanged を完全に回避したい場合があります。プロジェクトに不要な簿記コードを追加します。代わりにUpdate Controls .NETの使用を検討してください。
このブログ投稿をご覧ください: http://khason.net/dev/inotifypropertychanged-auto-wiring-or-how-to-get-rid-of-redundant-code