使用CodeContracts强制正确实现INotifyPropertyChanged - “需要未经证实”

时间:2009-10-25 20:04:57

标签: c# inotifypropertychanged code-contracts

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

无论如何要让它发挥作用吗?

3 个答案:

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

您可以通过多种方式为此案例启用静态分析/静态工具:

  1. 像马克所说,use lambda notation and extract string in run-time
  2. Write a custom FxCop rule
  3. Use an AOP tool to post-process code带有一些元标记。
  4. 对于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;
     }
}