5

我正在寻找一种简单的方法来强制执行 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;
}

反正有没有让这个工作?

4

3 回答 3

3

好的,首先,为此我个人使用MVVM 基金会的 ObservableObject 实现。这是一个仅调试构建的运行时检查,几乎与您的相同。

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 经验中总是足够的),它看起来肯定像一个“补丁”缺少静态检查。

您有几种方法可以为这种情况启用静态分析/静态工具:

  1. 就像 Marc 所说,使用 lambda 表示法并在运行时提取字符串
  2. 编写自定义 FxCop 规则
  3. 使用 AOP 工具对带有一些元标记的代码进行后处理。

至于 CodeContracts,我认为它还不够成熟,无法在静态分析中处理这种检查。想象一下,它必须解析你的 lambda,了解它如何因错误而失败propertyName,找到对该方法的所有调用,找出所有可能的输入,等等。它只是用于这种检查的错误工具。

于 2009-10-29T23:32:42.657 回答
1

我假设您的意思是使用静态分析工具?(我希望运行时检查至少可以正常工作 - 您可能可以将其保留在调试版本中)。我怀疑这是静态分析能够看透的东西——GetType().GetProperties()太复杂了,等等。

简而言之; 我对此表示怀疑... lambdas ( Expression) 是一种选择,但它们比仅传递一个字符串要慢得多。

于 2009-10-25T20:08:41.730 回答
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;
     }
}
于 2009-11-14T00:40:15.550 回答