[C#] INotifyPropertyChanged 구현-더 좋은 방법이 있습니까?

Microsoft는 INotifyPropertyChanged자동 속성에서와 같이 딱딱한 것을 구현 해야합니다.{get; set; notify;}
한다고 생각합니다. 아니면 합병증이 있습니까?

우리는 우리 자신의 속성에 ‘알림’과 같은 것을 구현할 수 있습니까? INotifyPropertyChanged수업 에 구현할 수있는 우아한 솔루션이 있습니까? 아니면 그것을 수행하는 유일한 방법은PropertyChanged 각 속성 이벤트를 입니다.

그렇지 않다면 PropertyChanged 이벤트 를 발생시키기 위해 코드를 자동 생성하는 무언가를 작성할 수 있습니까?



답변

postsharp와 같은 것을 사용하지 않고 사용하는 최소 버전은 다음과 같은 것을 사용합니다.

public class Data : INotifyPropertyChanged
{
    // boiler-plate
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
    protected bool SetField<T>(ref T field, T value, string propertyName)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return false;
        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }

    // props
    private string name;
    public string Name
    {
        get { return name; }
        set { SetField(ref name, value, "Name"); }
    }
}

각 속성은 다음과 같습니다.

    private string name;
    public string Name
    {
        get { return name; }
        set { SetField(ref name, value, "Name"); }
    }

크지 않은; 원하는 경우 기본 클래스로 사용할 수도 있습니다. 에서 bool반환 SetField하면 다른 논리를 적용하려는 경우 비 작동인지 알려줍니다.


또는 C # 5를 사용하면 더 쉽습니다.

protected bool SetField<T>(ref T field, T value,
    [CallerMemberName] string propertyName = null)
{...}

다음과 같이 호출 할 수 있습니다 :

set { SetField(ref name, value); }

컴파일러가 "Name"자동으로 추가합니다 .


C # 6.0을 사용하면 구현이 더 쉬워집니다.

protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

… 그리고 이제 C # 7로 :

protected void OnPropertyChanged(string propertyName)
   => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

protected bool SetField<T>(ref T field, T value,[CallerMemberName] string propertyName =  null)
{
    if (EqualityComparer<T>.Default.Equals(field, value)) return false;
    field = value;
    OnPropertyChanged(propertyName);
    return true;
}

private string name;
public string Name
{
    get => name;
    set => SetField(ref name, value);
}


답변

.Net 4.5부터는이를 수행하는 쉬운 방법이 있습니다.

.Net 4.5에는 새로운 발신자 정보 속성이 도입되었습니다.

private void OnPropertyChanged<T>([CallerMemberName]string caller = null) {
     // make sure only to call this if the value actually changes

     var handler = PropertyChanged;
     if (handler != null) {
        handler(this, new PropertyChangedEventArgs(caller));
     }
}

함수에 비교자를 추가하는 것이 좋습니다.

EqualityComparer<T>.Default.Equals

더 많은 예제 여기여기에

발신자 정보 참조 (C # 및 Visual Basic)


답변

Marc의 솔루션은 정말 마음에 들지만 리팩토링을 지원하지 않는 “매직 문자열”을 사용하지 않도록 약간 개선 할 수 있다고 생각합니다. 속성 이름을 문자열로 사용하는 대신 람다 식으로 만들 수 있습니다.

private string name;
public string Name
{
    get { return name; }
    set { SetField(ref name, value, () => Name); }
}

Marc의 코드에 다음 방법을 추가하면 트릭을 수행합니다.

protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
{
    if (selectorExpression == null)
        throw new ArgumentNullException("selectorExpression");
    MemberExpression body = selectorExpression.Body as MemberExpression;
    if (body == null)
        throw new ArgumentException("The body must be a member expression");
    OnPropertyChanged(body.Member.Name);
}

protected bool SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression)
{
    if (EqualityComparer<T>.Default.Equals(field, value)) return false;
    field = value;
    OnPropertyChanged(selectorExpression);
    return true;
}

BTW, 이것은 블로그 게시물 업데이트 URL 에서 영감을


답변

PropertyChanged 추가 기능 이있는 Fody 도 있습니다.

[ImplementPropertyChanged]
public class Person
{
    public string GivenNames { get; set; }
    public string FamilyName { get; set; }
}

… 컴파일시 속성 변경 알림을 삽입합니다.


답변

사람들은 성능에 조금 더주의를 기울여야한다고 생각합니다. 바인딩 할 개체가 많거나 (행이 10,000 개 이상인 그리드를 생각할 때) 개체의 값이 자주 변경되는 경우 (실시간 모니터링 앱) 실제로 UI에 영향을줍니다.

나는 여기와 다른 곳에서 발견 된 다양한 구현을 취하고 비교를했다. INotifyPropertyChanged 구현의 성능 비교를 확인하십시오 .


결과를 엿볼 수 있습니다.
구현 대 런타임


답변

내 블로그 ( http://timoch.com/blog/2013/08/annoyed-with-inotifypropertychange/) 에서 Bindable 클래스를 소개합니다. Bindable
은 사전을 속성 백으로 사용합니다. ref 매개 변수를 사용하여 자체 하위 필드를 관리하기 위해 서브 클래스에 필요한 오버로드를 추가하기가 쉽습니다.

  • 마술 줄 없음
  • 반사 없음
  • 기본 사전 조회를 억제하도록 개선 할 수 있습니다

코드:

public class Bindable : INotifyPropertyChanged {
    private Dictionary<string, object> _properties = new Dictionary<string, object>();

    /// <summary>
    /// Gets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <returns></returns>
    protected T Get<T>([CallerMemberName] string name = null) {
        Debug.Assert(name != null, "name != null");
        object value = null;
        if (_properties.TryGetValue(name, out value))
            return value == null ? default(T) : (T)value;
        return default(T);
    }

    /// <summary>
    /// Sets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <param name="name"></param>
    /// <remarks>Use this overload when implicitly naming the property</remarks>
    protected void Set<T>(T value, [CallerMemberName] string name = null) {
        Debug.Assert(name != null, "name != null");
        if (Equals(value, Get<T>(name)))
            return;
        _properties[name] = value;
        OnPropertyChanged(name);
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

다음과 같이 사용할 수 있습니다 :

public class Contact : Bindable {
    public string FirstName {
        get { return Get<string>(); }
        set { Set(value); }
    }
}


답변

실제로이 작업을 직접 시도 할 기회는 없었지만 다음에 INotifyPropertyChanged에 대한 큰 요구 사항으로 프로젝트를 설정할 때 컴파일 타임에 코드를 주입 하는 Postsharp 속성을 작성하려고합니다 . 다음과 같은 것 :

[NotifiesChange]
public string FirstName { get; set; }

될 것입니다:

private string _firstName;

public string FirstName
{
   get { return _firstname; }
   set
   {
      if (_firstname != value)
      {
          _firstname = value;
          OnPropertyChanged("FirstName")
      }
   }
}

이것이 실제로 작동하는지 확실하지 않으며 앉아서 시도해야하지만 왜 그런지 모르겠습니다. 하나 이상의 OnPropertyChanged를 트리거해야하는 상황에 대해 일부 매개 변수를 수락해야 할 수도 있습니다 (예를 들어 위 클래스에 FullName 속성이있는 경우).

현재 Resharper에서 사용자 정의 템플릿을 사용하고 있지만 그로 인해 모든 속성이 너무 길어지고 있습니다.


아, 빠른 Google 검색 (이 글을 작성하기 전에 수행해야했던 것)은 적어도 한 사람이 여기 전에 이와 같은 작업을 수행했음을 보여줍니다 . 내가 생각한 바가 아니라 이론이 훌륭하다는 것을 보여줄만큼 충분히 가까웠다.