我正在寻找一种简单的方法来强制执行INotifyPropertyChanged的正确实现,即当引发PropertyChanged时,它必须引用实际定义的属性。我尝试使用Microsoft的新CodeContract工具执行此操作,但我不断收到警告“CodeContracts:需要未经证实”。这是我的代码......
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;
}
无论如何要让它发挥作用吗?
答案 0 :(得分:3)
好的,首先,为此我个人使用MVVM foundation中的ObservableObject实现。它只是一个DEBUG-build运行时检查,几乎与你的相同。
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体验中总是足够的),它肯定看起来像一个缺少静态检查的“补丁”。
您可以通过多种方式为此案例启用静态分析/静态工具:
对于CodeContracts,我认为它还不够成熟,无法在静态分析中处理这种检查。想象一下,它必须解析你的lambda,理解错误propertyName
如何失败,找到对这个方法的所有调用,找出所有可能的输入等等。这种检查只是一个错误的工具
答案 1 :(得分:1)
我认为你的意思是静态分析工具? (我希望运行时检查能够起作用,至少 - 你可能会把它留在调试版本中)。我怀疑这是静态分析能够看到的东西 - GetType().GetProperties()
太复杂了等等。
总之;我怀疑它... lambdas(Expression
)是一个选项,但它们比传递一个字符串慢得多。
答案 2 :(得分:1)
我过去这样做的方式是使用我们的好朋友Lambda。通过使用表达式,我们可以将属性本身传递给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;
}
}