CodeContractsでINotifyPropertyChangedの正しい実装を強制する-“未確認が必要です”
-
06-07-2019 - |
質問
私はINotifyPropertyChangedの正しい実装を強制する簡単な方法を探しています。つまり、PropertyChangedが発生した場合、実際に定義されたプロパティを参照する必要があります。 Microsoftの新しいCodeContractツールを使用してこれを試しましたが、「CodeContracts:require unproven」という警告が引き続き表示されます。これが私のコードです...
public sealed class MyClass : INotifyPropertyChanged
{
private int myProperty;
public int MyProperty
{
get
{
return myProperty;
}
set
{
if (myProperty == value)
{
return;
}
myProperty = value;
OnPropertyChanged("MyProperty");
}
}
private void OnPropertyChanged(string propertyName)
{
Contract.Requires(GetType().GetProperties().Any(x => x.Name == propertyName));
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
とにかくこれを機能させる方法はありますか?
解決
あなたは静的解析ツールを意味すると思いますか(少なくとも、ランタイムチェックが機能することを期待します。おそらく、デバッグビルドのままにしておくことができます)。これは、静的解析で見ることができるものだとは思わない- GetType()。GetProperties()
は単純に複雑すぎる、など
要するに;私はそれを疑います...ラムダ( Expression
)はオプションですが、文字列だけを渡すよりもはるかに遅いです。
他のヒント
まず、この目的のために、私は個人的に MVVM Foundation のObservableObject実装を使用しています。これはDEBUGビルドのみのランタイムチェックであり、ほぼ同じです。
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
[Conditional("DEBUG")]
[DebuggerStepThrough]
public void VerifyPropertyName(string propertyName)
{
// Verify that the property name matches a real,
// public, instance property on this object.
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
string msg = "Invalid property name: " + propertyName;
if (this.ThrowOnInvalidPropertyName)
throw new Exception(msg);
else
Debug.Fail(msg);
}
}
おそらく最も簡単な方法ですが、いくつかの欠点があります:いくつかの基本クラスから継承できる必要があり、ランタイムでのみ機能します(これは私のwpfエクスペリエンスでは常に十分でしたが)、確かに「パッチ」静的チェックがない場合。
この場合、静的分析/静的ツールを有効にする方法はいくつかあります:
- Like Marc氏、ラムダ表記を使用して抽出実行時の文字列。
- カスタムFxCopルールを作成します。
- AOPツールを使用して、メタマークアップを使用してコードを後処理します。
CodeContractsに関しては、静的分析でこの種のチェックを処理するのにまだ十分に成熟していないと思います。想像してください。ラムダを解析し、間違った propertyName
によってラムダがどのように失敗するかを理解し、このメソッドへのすべての呼び出しを見つけ、すべての可能な入力を見つけなければなりません。チェックの種類。
過去にこれを行った方法は、私たちの良き友人ラムダを使用することです。式を使用することで、プロパティ自体をOnPropertyChangesの実装に渡し、式ツリーを使用してプロパティを抽出できます。これにより、PropertyChangedイベントを発生させているメンバーのコンパイル時のチェックが可能になります。
もちろん、Expressionの使用は、必要なパフォーマンスのタイプに完全に依存します。
以下のコードスニペットを参照してください:
using System;
using System.Linq;
using System.ComponentModel;
using System.Linq.Expressions;
namespace OnNotifyUsingLambda
{
public class MainClass : INotifyPropertyChanged
{
public static void Main (string[] args) { new MainClass().Run();}
public void Run()
{
this.PropertyChanged += (sender, e) => Console.WriteLine(e.PropertyName);
MyProperty = "Hello";
}
private string myProperty;
public string MyProperty
{
get
{
return myProperty;
}
set
{
myProperty = value;
// call our OnPropertyChanged with our lamba expression, passing ourselves.
// voila compile time checking that we haven't messed up!
OnPropertyChanged(x => x.MyProperty);
}
}
/// <summary>
/// Fires the PropertyChanged for a property on our class.
/// </summary>
/// <param name="property">
/// A <see cref="Expression<Func<MainClass, System.Object>>"/> that contains the
/// property we want to raise the event for.
/// </param>
private void OnPropertyChanged (Expression<Func<MainClass, object>> property)
{
// pull out the member expression (ie mainClass.MyProperty)
var expr = (MemberExpression)property.Body;
if (PropertyChanged != null)
{
// Extract everything after the period, which is our property name.
var propName = expr.ToString ().Split (new[] { '.' })[1];
PropertyChanged (this, new PropertyChangedEventArgs(propName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}