2009-10-25 3 views
5

INotifyPropertyChanged를 올바르게 구현하는 쉬운 방법을 찾고 있습니다. 즉, PropertyChanged가 발생하면 실제로 정의 된 속성을 참조해야합니다. Microsoft의 새로운 CodeContract 도구로이 작업을 시도했지만 "CodeContracts : unproven"이 필요합니다. 여기 내 코드는 ...CodeContracts - "증명되지 않음"으로 INotifyPropertyChanged의 올바른 구현 시행

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

정적 검사하지 않는 반사 계약의 유효성을 검사합니다. http://social.msdn.microsoft.com/Forums/en-US/codecontracts/thread/37e28a50-bf64-4b02-b384-f55117735690/ – hwiechers

답변

1

정적 분석 도구를 사용한다고 생각하십니까? (나는 적어도 런타임 체크가 작동 할 것으로 기대한다. 그리고 아마 디버그 빌드에 남겨 둘 수있다). 나는 이것이 정적 분석이 볼 수있게 될 것이라는 점을 의심한다 - GetType().GetProperties()은 단순히 너무 복잡하다.

짧은; 나는 그것을 의심 스럽다 ... lambdas (Expression)는 옵션이지만 문자열 만 전달하는 것보다 훨씬 느리다.

+0

기술적으로 표현식은 느리지 만 미세한 최적화는 걱정입니다. 그것은 보통 노력의 가치가 없습니다. (필자의 벤치 마크에서 우리는 1800 나노초의 차이를 말하는데, 이는 2 마이크로 초 미만이다.) – Bevan

+0

@Bevan in C# 5, 이것은 완전히 사라져 버립니다. - INotifyPropertyChanged와 같은 것들에 대해 화려한 이름을 얻기위한 새로운 멋진 메커니즘이 있습니다. –

+0

동의 함, [CallerMemberName] (http://msdn.microsoft .com/en-us/library/system.runtime.compilerservices.callermembername 속성 % 28v = vs.110 % 29.aspx) 속성이 가장 유용합니다. – Bevan

3

우선,이 목적을 위해 저는 개인적으로에서 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-경험 이었지만) 런타임 작동을 확실하게 누락 된 정적 검사를위한 "패치"처럼 보입니다. 마크처럼

  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 일부 메타 마크 업과 함께

CodeContracts에 관해서는 정적 분석에서 이러한 종류의 검사를 처리 할 수있을만큼 성숙한 것은 아니라고 생각합니다. 상상해보십시오, 당신의 람다를 파싱해야하고, 잘못된 방법으로 실패 할 수있는 방법을 이해해야합니다. propertyName,이 방법에 대한 모든 호출을 찾아 낼 수있는 모든 입력 등을 찾아야합니다. 그런 종류의 검사에는 잘못된 도구 일뿐입니다.

+0

+1 :이 목적으로 ConditionalAttribute를 사용하는 것은 실제로 매우 좋습니다. – Juliet

1

나는 과거에 이것을 한 방법은 우리의 좋은 친구 람다를 사용하는 것입니다. Expression을 사용하여 OnPropertyChanges 구현에 속성 자체를 전달할 수 있으며 Expression 트리를 사용하여 속성을 추출 할 수 있습니다. 이렇게하면 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; 
    } 
}