INotifyPropertyChanged 구현-더 좋은 방법이 있습니까?


647

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

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

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



7
위의 링크는 죽었습니다. github.com/SimonCropp/NotifyPropertyWeaver
prime23

2
대신 DependencyObject 및 DependencyProperties를 사용할 수 있습니다. 하아! 나는 웃겼다.
Phil


5
당시 C #에 대한 변경은 불가능했습니다. 그래서 MVVM이 태어 났을 때 나는이 문제를 해결하기 위해 많은 노력을 기울이지 않았으며 Patterns & Practices 팀이 그 과정에서 약간의 노력을 기울 였음을 알고 있습니다 (따라서 MEF도 그 일부로 얻었습니다) 연구 스레드). 오늘은 [CallerMemberName]이 위의 답변이라고 생각합니다.
Scott Barnes

답변:


633

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);
}

4
좋은 트릭 마크! 속성 이름 대신 람다 식을 사용하도록 개선 할 것을 제안했습니다. 내 답변보기
Thomas Levesque

7
@ 토마스-람다는 모두 훌륭하고 훌륭하지만 실제로 매우 간단한 것에 많은 오버 헤드를 추가합니다. 편리한 트릭이지만 항상 실용적이지는 않습니다.
Marc Gravell

14
@Marc-예, 아마도 성능을 저하시킬 수 있습니다 ... 그러나 컴파일 타임에 확인되고 "이름 바꾸기"명령으로 정확하게 리팩터링된다는 사실이 정말 마음에 듭니다
Thomas Levesque

4
@Gusdor 다행히도, C # 5 타협 할 필요가 없습니다 - 당신이를 통해 모두의 최선을 얻을 수 있습니다 (Pedro77 노트 등)[CallerMemberName]
마크 Gravell

4
@Gusdor 언어와 프레임 워크는 분리되어 있습니다. C # 5 컴파일러, .NET 4를 대상으로 하고 누락 된 속성을 직접 추가하면 됩니다. 제대로 작동합니다. 이름이 정확하고 네임 스페이스에 있어야합니다. 특정 어셈블리에있을 필요는 없습니다.
Marc Gravell

196

.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)


12
훌륭한! 그러나 왜 일반적인가요?
abatishchev

@ abatishchev 반드시 필요한 것은 아니라고 생각합니다. 함수도 속성을 설정한다는 아이디어로 놀고있었습니다. 답변을 업데이트하여 전체 솔루션을 제공 할 수 있는지 확인하겠습니다. 추가 예제는 그 동안 잘 작동합니다.
Daniel Little

3
그것은 C # 5.0에 의해 소개되었습니다. .net 4.5와 관련이 없지만 이것은 훌륭한 솔루션입니다!
J. Lennon

5
@제이. Lennon .net 4.5는 모든 속성이 msdn.microsoft.com/en-au/library/
Daniel Little

@Lavinski는 응용 프로그램을 .NET 3.5로 변경하고 어떤 기능이 작동하는지 확인합니다 (2012 년 대비)
J. Lennon

162

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 에서 영감을


6
이 메소드 인 ReactiveUI를 사용하는 프레임 워크가 하나 이상 있습니다 .
AlSki

아주 늦게, 이것은 성찰을 거쳐야한다는 것을 의미했습니다. 괜찮을 수도 있지만 속성을 설정하는 것이 응용 프로그램이 여러주기에 소비되는 곳이 아닙니다.
Bruno Brant

1
@BrunoBrant 성능 저하가 확실합니까? 블로그 게시물에 따르면 리플렉션은 런타임이 아닌 컴파일 시간 동안 발생합니다 (예 : 정적 리플렉션).
Nathaniel Elkins 2016 년

6
귀하의 OnPropertyChanged <T> 전체가 C # 6 연산자의 이름으로 더 이상 사용되지 않으므로이 괴물을 조금 더 매끄럽게 만듭니다.
Traubenfuchs

5
@Traubenfuchs, 실제로, C # 5의 CallerMemberName 속성은 아무것도 전달할 필요가 없기 때문에 훨씬 더 단순 해집니다.
Thomas Levesque

120

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

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

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


7
나는 이것이 영업 이익은 그들이 물었을 때 무엇을 찾고 있었다 정확하게 생각 "우리 스스로가 우리의 속성에서 '알림 서비스'같은 것을 구현할 수있는 클래스에서에서 INotifyPropertyChanged를 구현하기위한 우아한 해결책이 있습니다."
Ashoat

3
이것은 실제로 유일하게 우아한 솔루션이며 @CADbloke가 말한 것처럼 완벽하게 작동합니다. 그리고 위버에 대해서도 회의적이지만 IL 코드를 확인 / 재확인했으며 완벽하고 간단합니다. 필요한 모든 것을하고 다른 것은 없습니다. 또한 NotifyOnProp ..., OnNotify ...가 중요하지 않은지 여부에 관계없이 기본 클래스에서 지정한 메소드 이름을 후크하고 호출하므로 보유하고 있고 INotify를 구현하는 모든 기본 클래스와 잘 작동합니다. .
NSGaga - 대부분 비활성

1
위버가하는 일을 쉽게 재확인하고, 빌드 출력 창을 살펴보고, 직조 한 모든 PropertyChanged 항목을 나열합니다. 정규식 패턴과 함께 VScolorOutput 확장을 사용하면 "Fody/.*?:",LogCustom2,True"Custom 2"색으로 강조 표시됩니다. 밝은 분홍색으로 만들었으므로 쉽게 찾을 수 있습니다. 그냥 Fody everything, 그것은 반복적 인 타이핑이 많은 것을하는 가장 쉬운 방법입니다.
CAD bloke

@ mahmoudnezarsarhan 아니오, 아닙니다. 구성 방법에 약간의 변화가 있었지만 Fody PropertyChanged 는 여전히 살아 있고 활성화되어 있습니다.
Larry

65

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

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


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


14
-1 : 성능 오버 헤드가 없습니다. CallerMemberName이 컴파일 타임에 리터럴 값으로 변경됩니다. 앱을 시도하고 디 컴파일하십시오.
JYL

여기에 따라 질문과 답변입니다 : stackoverflow.com/questions/22580623/…
uli78

1
@ JYL, CallerMemberName이 큰 오버 헤드를 추가하지 않은 것이 맞습니다. 마지막으로 시도했을 때 잘못된 것을 구현 했어야합니다. 나중에 CallerMemberName 및 Fody 구현에 대한 벤치 마크를 반영하도록 블로그를 업데이트하고 답변하겠습니다.
Peijen

1
UI에 10,000+의 격자가있는 경우 페이지 당 10, 50, 100, 250 개의 적중 만 표시하는 페이징과 같이 성능을 처리하는 방법을 결합해야합니다.
Austin Rhymer

Austin Rhymer, 데이터 +50 사용 데이터 가상화를 사용하는 경우 모든 데이터를로드 할 필요가 없으며 현재 스콜 링 표시 영역에 표시되는 데이터 만로드합니다!
Bilal

38

내 블로그 ( 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); }
    }
}

2
이것은 좋은 해결책이지만, 유일한 단점은 권투 / 언 박싱과 관련하여 약간의 성능 저하가 있다는 것입니다.
MCattle

1
내가 사용하는 것이 좋습니다 것입니다 protected T Get<T>(T defaultValue, [CallerMemberName] string name = null)확인도하고 if (_properties.ContainsKey(name) && Equals(value, Get<T>(default(T), name)))(인상 및 기본값으로 첫 세트를 저장) 설정에
미켈

1
@Miquel 사용자 정의 기본값에 대한 지원을 추가하면 유용 할 수 있지만 실제로 값이 변경되었을 때 변경된 이벤트 만 발생하도록주의해야합니다. 속성을 동일한 값으로 설정하면 이벤트가 발생하지 않아야합니다. 나는 대부분의 경우 무해하다는 것을 인정해야하지만 UI 응답 성을 파괴하는 이벤트와 함께 수천 시간을 동일한 값으로 설정하는 속성으로 꽤 많이 배웠습니다.
TiMoch

1
@stakx 나는 undo / redo를위한 memento 패턴을 지원하거나 nhibernate를 사용할 수없는 어플리케이션에서 작업 단위 패턴을 가능하게하기 위해 이것에 기반을 둔 몇 가지 어플리케이션을 가지고있다
TiMoch

1
짧은 표기법, 동적 프록시 항목 없음, IL 메들 링 없음 등이 특정 솔루션을 정말 좋아합니다. 그러나 Get return을 동적으로 만들어 Get마다 매번 T를 지정할 필요가 없어서 더 짧게 만들 수 있습니다 . 나는 이것이 런타임 성능에 영향을 미친다는 것을 알고 있지만, 이제 getter와 setter의 코드는 항상 동일하고 한 줄로 주님을 찬양 할 수 있습니다! 추신 : 값 유형의 기본값을 동적으로 반환 할 때 Get 메서드 내에서 기본 클래스를 작성할 때 한 번 더주의해야합니다. 항상 올바른 기본값을 반환해야합니다 (완료 가능)
evilkos

15

실제로이 작업을 직접 시도 할 기회는 없었지만 다음에 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 검색 (이 글을 작성하기 전에 수행해야했던 것)은 적어도 한 사람이 여기 전에 이와 같은 작업을 수행했음을 보여줍니다 . 내가 생각한 바가 아니라 이론이 훌륭하다는 것을 보여줄만큼 충분히 가까웠다.


6
Fody라는 무료 도구는 일반 컴파일 타임 코드 인젝터와 같은 기능을하는 것으로 보입니다. PropertyChanged 및 PropertyChanging 플러그인 패키지와 마찬가지로 Nuget에서 다운로드 할 수 있습니다.
Triynko

11

더 나은 방법은 확실히 존재합니다. 여기있어:

유용한 기사를 기반으로 단계별 튜토리얼이 축소되었습니다 .

  • 새로운 프로젝트 만들기
  • 프로젝트에 캐슬 코어 패키지 설치

설치 패키지 Castle.Core

  • mvvm light 라이브러리 만 설치

설치 패키지 MvvmLightLibs

  • 프로젝트에 두 개의 클래스를 추가하십시오.

알리미 인터셉터

public class NotifierInterceptor : IInterceptor
    {
        private PropertyChangedEventHandler handler;
        public static Dictionary<String, PropertyChangedEventArgs> _cache =
          new Dictionary<string, PropertyChangedEventArgs>();

        public void Intercept(IInvocation invocation)
        {
            switch (invocation.Method.Name)
            {
                case "add_PropertyChanged":
                    handler = (PropertyChangedEventHandler)
                              Delegate.Combine(handler, (Delegate)invocation.Arguments[0]);
                    invocation.ReturnValue = handler;
                    break;
                case "remove_PropertyChanged":
                    handler = (PropertyChangedEventHandler)
                              Delegate.Remove(handler, (Delegate)invocation.Arguments[0]);
                    invocation.ReturnValue = handler;
                    break;
                default:
                    if (invocation.Method.Name.StartsWith("set_"))
                    {
                        invocation.Proceed();
                        if (handler != null)
                        {
                            var arg = retrievePropertyChangedArg(invocation.Method.Name);
                            handler(invocation.Proxy, arg);
                        }
                    }
                    else invocation.Proceed();
                    break;
            }
        }

        private static PropertyChangedEventArgs retrievePropertyChangedArg(String methodName)
        {
            PropertyChangedEventArgs arg = null;
            _cache.TryGetValue(methodName, out arg);
            if (arg == null)
            {
                arg = new PropertyChangedEventArgs(methodName.Substring(4));
                _cache.Add(methodName, arg);
            }
            return arg;
        }
    }

ProxyCreator

public class ProxyCreator
{
    public static T MakeINotifyPropertyChanged<T>() where T : class, new()
    {
        var proxyGen = new ProxyGenerator();
        var proxy = proxyGen.CreateClassProxy(
          typeof(T),
          new[] { typeof(INotifyPropertyChanged) },
          ProxyGenerationOptions.Default,
          new NotifierInterceptor()
          );
        return proxy as T;
    }
}
  • 보기 모델을 작성하십시오 (예 :

-

 public class MainViewModel
    {
        public virtual string MainTextBox { get; set; }

        public RelayCommand TestActionCommand
        {
            get { return new RelayCommand(TestAction); }
        }

        public void TestAction()
        {
            Trace.WriteLine(MainTextBox);
        }
    }
  • xaml에 바인딩을 넣으십시오.

    <TextBox Text="{Binding MainTextBox}" ></TextBox>
    <Button Command="{Binding TestActionCommand}" >Test</Button>
  • 코드 숨김 파일 MainWindow.xaml.cs에 코드 줄을 다음과 같이 입력하십시오.

DataContext = ProxyCreator.MakeINotifyPropertyChanged<MainViewModel>();

  • 즐겨.

여기에 이미지 설명을 입력하십시오

주의!!! 모든 경계 속성은 재정의를 위해 캐슬 프록시가 사용했기 때문에 키워드 가상으로 장식되어야합니다.


사용중인 Castle 버전을 알고 싶습니다. 내가 3.3.0를 사용하고하고 CreateClassProxy 방법은 이러한 매개 변수가없는 : type, interfaces to apply, interceptors.
IAbstract

신경 쓰지 마라, 나는 일반적인 CreateClassProxy<T>방법 을 사용하고 있었다 . 일반적인 방법으로 왜 그렇게 제한되는지 궁금해하는 많은 다른 ... hmmm. :(
IAbstract

7

매우 AOP와 유사한 접근 방식은 INotifyPropertyChanged 항목을 이미 인스턴스화 된 객체에 즉시 주입하는 것입니다. Castle DynamicProxy와 같은 방법으로이를 수행 할 수 있습니다. 이 기술을 설명하는 기사는 다음과 같습니다.

기존 객체에 INotifyPropertyChanged 추가


5

여기를 봐 : http://dotnet-forum.de/blogs/thearchitect/archive/2012/11/01/die-optimale-implementierung-des-inotifypropertychanged-interfaces.aspx

독일어로 작성되었지만 ViewModelBase.cs를 다운로드 할 수 있습니다. cs-File의 모든 주석은 영어로 작성되었습니다.

이 ViewModelBase-Class를 사용하면 잘 알려진 종속성 속성과 비슷한 바인딩 가능한 속성을 구현할 수 있습니다.

public string SomeProperty
{
    get { return GetValue( () => SomeProperty ); }
    set { SetValue( () => SomeProperty, value ); }
}

1
링크가 끊어졌습니다.
Guge

4

Marc의 답변에서 수정 된 Thomas의 답변을 기반으로 반영 속성 변경 코드를 기본 클래스로 바꿨습니다.

public abstract class PropertyChangedBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

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

    protected void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
    {
        if (selectorExpression == null)
            throw new ArgumentNullException("selectorExpression");
        var me = selectorExpression.Body as MemberExpression;

        // Nullable properties can be nested inside of a convert function
        if (me == null)
        {
            var ue = selectorExpression.Body as UnaryExpression;
            if (ue != null)
                me = ue.Operand as MemberExpression;
        }

        if (me == null)
            throw new ArgumentException("The body must be a member expression");

        OnPropertyChanged(me.Member.Name);
    }

    protected void SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression, params Expression<Func<object>>[] additonal)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return;
        field = value;
        OnPropertyChanged(selectorExpression);
        foreach (var item in additonal)
            OnPropertyChanged(item);
    }
}

사용법은 추가 속성을 전달하여 알릴 수 있다는 점을 제외하면 Thomas의 답변과 동일합니다. 그리드에서 새로 고쳐야하는 계산 열을 처리하는 데 필요했습니다.

private int _quantity;
private int _price;

public int Quantity 
{ 
    get { return _quantity; } 
    set { SetField(ref _quantity, value, () => Quantity, () => Total); } 
}
public int Price 
{ 
    get { return _price; } 
    set { SetField(ref _price, value, () => Price, () => Total); } 
}
public int Total { get { return _price * _quantity; } }

DataGridView를 통해 노출 된 BindingList에 저장된 항목 컬렉션을 구동합니다. 그리드에 수동 Refresh () 호출을 수행 할 필요가 없습니다.


4

Yappi 라는 내 접근 방식을 소개하겠습니다 . Caste Project의 Dynamic Proxy와 같은 기존 객체 또는 유형에 새로운 기능을 추가하여 런타임 프록시 클래스 생성기에 속합니다.

기본 클래스에서 INotifyPropertyChanged를 한 번 구현 한 다음 파생 된 클래스를 다음 스타일로 선언하여 새 속성에 대해 INotifyPropertyChanged를 계속 지원합니다.

public class Animal:Concept
{
    protected Animal(){}
    public virtual string Name { get; set; }
    public virtual int Age { get; set; }
}

파생 클래스 또는 프록시 구성의 복잡성은 다음 줄 뒤에 숨길 수 있습니다.

var animal = Concept.Create<Animal>.New();

그리고 모든 INotifyPropertyChanged 구현 작업은 다음과 같이 수행 할 수 있습니다.

public class Concept:INotifyPropertyChanged
{
    //Hide constructor
    protected Concept(){}

    public static class Create<TConcept> where TConcept:Concept
    {
        //Construct derived Type calling PropertyProxy.ConstructType
        public static readonly Type Type = PropertyProxy.ConstructType<TConcept, Implementation<TConcept>>(new Type[0], true);
        //Create constructing delegate calling Constructor.Compile
        public static Func<TConcept> New = Constructor.Compile<Func<TConcept>>(Type);
    }


    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(PropertyChangedEventArgs eventArgs)
    {
        var caller = PropertyChanged;
        if(caller!=null)
        {
            caller(this, eventArgs);
        }
    }

    //define implementation
    public class Implementation<TConcept> : DefaultImplementation<TConcept> where TConcept:Concept
    {
        public override Func<TBaseType, TResult> OverrideGetter<TBaseType, TDeclaringType, TConstructedType, TResult>(PropertyInfo property)
        {
            return PropertyImplementation<TBaseType, TDeclaringType>.GetGetter<TResult>(property.Name);
        }
        /// <summary>
        /// Overriding property setter implementation.
        /// </summary>
        /// <typeparam name="TBaseType">Base type for implementation. TBaseType must be TConcept, and inherits all its constraints. Also TBaseType is TDeclaringType.</typeparam>
        /// <typeparam name="TDeclaringType">Type, declaring property.</typeparam>
        /// <typeparam name="TConstructedType">Constructed type. TConstructedType is TDeclaringType and TBaseType.</typeparam>
        /// <typeparam name="TResult">Type of property.</typeparam>
        /// <param name="property">PropertyInfo of property.</param>
        /// <returns>Delegate, corresponding to property setter implementation.</returns>
        public override Action<TBaseType, TResult> OverrideSetter<TBaseType, TDeclaringType, TConstructedType, TResult>(PropertyInfo property)
        {
            //This code called once for each declared property on derived type's initialization.
            //EventArgs instance is shared between all events for each concrete property.
            var eventArgs = new PropertyChangedEventArgs(property.Name);
            //get delegates for base calls.
            Action<TBaseType, TResult> setter = PropertyImplementation<TBaseType, TDeclaringType>.GetSetter<TResult>(property.Name);
            Func<TBaseType, TResult> getter = PropertyImplementation<TBaseType, TDeclaringType>.GetGetter<TResult>(property.Name);

            var comparer = EqualityComparer<TResult>.Default;

            return (pthis, value) =>
            {//This code executes each time property setter is called.
                if (comparer.Equals(value, getter(pthis))) return;
                //base. call
                setter(pthis, value);
                //Directly accessing Concept's protected method.
                pthis.OnPropertyChanged(eventArgs);
            };
        }
    }
}

리팩토링에 완벽하게 안전하며 유형 구성 후 반사를 사용하지 않으며 충분히 빠릅니다.


TDeclarationtype 매개 변수 가 필요 PropertyImplementation합니까? 확실히 getter / setter를 호출하여 적절한 유형을 찾을 수 TImplementation있습니까?
앤드류 Savinykh

TImplementation은 대부분의 경우 작동합니다. 예외는 다음과 같습니다. 1. "new"C # keyvord로 다시 정의 된 속성 2. 명시 적 인터페이스 구현의 속성.
Kelqualyn

3

이 모든 대답은 매우 좋습니다.

내 솔루션은 코드 스 니펫을 사용하여 작업을 수행하고 있습니다.

PropertyChanged 이벤트에 대한 가장 간단한 호출을 사용합니다.

이 스 니펫을 저장하고 'fullprop'스 니펫을 사용할 때 사용하십시오.

위치는 Visual Studio의 'Tools \ Code Snippet Manager ...'메뉴에서 찾을 수 있습니다.

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
    <CodeSnippet Format="1.0.0">
        <Header>
            <Title>inotifypropfull</Title>
            <Shortcut>inotifypropfull</Shortcut>
            <HelpUrl>http://ofirzeitoun.wordpress.com/</HelpUrl>
            <Description>Code snippet for property and backing field with notification</Description>
            <Author>Ofir Zeitoun</Author>
            <SnippetTypes>
                <SnippetType>Expansion</SnippetType>
            </SnippetTypes>
        </Header>
        <Snippet>
            <Declarations>
                <Literal>
                    <ID>type</ID>
                    <ToolTip>Property type</ToolTip>
                    <Default>int</Default>
                </Literal>
                <Literal>
                    <ID>property</ID>
                    <ToolTip>Property name</ToolTip>
                    <Default>MyProperty</Default>
                </Literal>
                <Literal>
                    <ID>field</ID>
                    <ToolTip>The variable backing this property</ToolTip>
                    <Default>myVar</Default>
                </Literal>
            </Declarations>
            <Code Language="csharp">
                <![CDATA[private $type$ $field$;

    public $type$ $property$
    {
        get { return $field$;}
        set { 
            $field$ = value;
            var temp = PropertyChanged;
            if (temp != null)
            {
                temp(this, new PropertyChangedEventArgs("$property$"));
            }
        }
    }
    $end$]]>
            </Code>
        </Snippet>
    </CodeSnippet>
</CodeSnippets>

원하는대로 통화를 수정할 수 있습니다 (위의 솔루션을 사용하기 위해)


2

.NET 4.5에서 역학을 사용하는 경우 걱정할 필요가 없습니다 INotifyPropertyChanged.

dynamic obj = new ExpandoObject();
obj.Name = "John";

Name이 일부 컨트롤에 바인딩되어 있으면 제대로 작동합니다.


1
이것을 사용하면 어떤 단점이 있습니까?
juFo September

2

또 다른 결합 솔루션은 StackFrame을 사용하는 것입니다.

public class BaseViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void Set<T>(ref T field, T value)
    {
        MethodBase method = new StackFrame(1).GetMethod();
        field = value;
        Raise(method.Name.Substring(4));
    }

    protected void Raise(string propertyName)
    {
        var temp = PropertyChanged;
        if (temp != null)
        {
            temp(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

용법:

public class TempVM : BaseViewModel
{
    private int _intP;
    public int IntP
    {
        get { return _intP; }
        set { Set<int>(ref _intP, value); }
    }
}

2
그렇게 빠른가요? 일부 권한 요구 사항에 바인딩 된 스택 프레임에 액세스 할 수 없습니까? async / await 사용의 맥락에서 강력합니까?
Stéphane Gourichon

@ StéphaneGourichon 아니오, 그렇지 않습니다. 스택 프레임에 액세스한다는 것은 대부분의 경우 상당한 성능 저하를 의미합니다.
Bruno Brant


인라인은 get_Foo릴리스 모드 에서 메소드를 숨길 수 있습니다 .
bytecode77

2

재사용을 위해 기본 라이브러리에서 확장 메소드를 작성했습니다.

public static class INotifyPropertyChangedExtensions
{
    public static bool SetPropertyAndNotify<T>(this INotifyPropertyChanged sender,
               PropertyChangedEventHandler handler, ref T field, T value, 
               [CallerMemberName] string propertyName = "",
               EqualityComparer<T> equalityComparer = null)
    {
        bool rtn = false;
        var eqComp = equalityComparer ?? EqualityComparer<T>.Default;
        if (!eqComp.Equals(field,value))
        {
            field = value;
            rtn = true;
            if (handler != null)
            {
                var args = new PropertyChangedEventArgs(propertyName);
                handler(sender, args);
            }
        }
        return rtn;
    }
}

이것은 CallerMemberNameAttribute 때문에 .Net 4.5에서 작동합니다 . 이전 .Net 버전에서 사용하려면 메소드 선언을 다음과 같이 변경해야합니다....,[CallerMemberName] string propertyName = "", ......,string propertyName, ...

용법:

public class Dog : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    string _name;

    public string Name
    {
        get { return _name; }
        set
        {
            this.SetPropertyAndNotify(PropertyChanged, ref _name, value);
        }
    }
}

2

나는 이런 식으로 해결했다 (조금 힘들지만 런타임에 확실히 더 빠르다).

VB에서는 (죄송하지만 C #에서는 번역이 어렵다고 생각합니다) RE로 다음과 같이 대체합니다.

(?<Attr><(.*ComponentModel\.)Bindable\(True\)>)( |\r\n)*(?<Def>(Public|Private|Friend|Protected) .*Property )(?<Name>[^ ]*) As (?<Type>.*?)[ |\r\n](?![ |\r\n]*Get)

와:

Private _${Name} As ${Type}\r\n${Attr}\r\n${Def}${Name} As ${Type}\r\nGet\r\nReturn _${Name}\r\nEnd Get\r\nSet (Value As ${Type})\r\nIf _${Name} <> Value Then \r\n_${Name} = Value\r\nRaiseEvent PropertyChanged(Me, New ComponentModel.PropertyChangedEventArgs("${Name}"))\r\nEnd If\r\nEnd Set\r\nEnd Property\r\n

이 transofrm 모든 코드는 다음과 같습니다.

<Bindable(True)>
Protected Friend Property StartDate As DateTime?

Private _StartDate As DateTime?
<Bindable(True)>
Protected Friend Property StartDate As DateTime?
    Get
        Return _StartDate
    End Get
    Set(Value As DateTime?)
        If _StartDate <> Value Then
            _StartDate = Value
            RaiseEvent PropertyChange(Me, New ComponentModel.PropertyChangedEventArgs("StartDate"))
        End If
    End Set
End Property

그리고 더 읽기 쉬운 코드를 원한다면 다음과 같이 대체 할 수 있습니다.

Private _(?<Name>.*) As (?<Type>.*)[\r\n ]*(?<Attr><(.*ComponentModel\.)Bindable\(True\)>)[\r\n ]*(?<Def>(Public|Private|Friend|Protected) .*Property )\k<Name> As \k<Type>[\r\n ]*Get[\r\n ]*Return _\k<Name>[\r\n ]*End Get[\r\n ]*Set\(Value As \k<Type>\)[\r\n ]*If _\k<Name> <> Value Then[\r\n ]*_\k<Name> = Value[\r\n ]*RaiseEvent PropertyChanged\(Me, New (.*ComponentModel\.)PropertyChangedEventArgs\("\k<Name>"\)\)[\r\n ]*End If[\r\n ]*End Set[\r\n ]*End Property

${Attr} ${Def} ${Name} As ${Type}

set 메소드의 IL 코드를 교체하려고하지만 IL에서 많은 컴파일 된 코드를 작성할 수 없습니다. 하루에 작성하면 말해 줄 것입니다!


2

나는 이것을 발췌 문장으로 유지합니다. C # 6은 핸들러 호출을위한 멋진 구문을 추가합니다.

// INotifyPropertyChanged

public event PropertyChangedEventHandler PropertyChanged;

private void Set<T>(ref T property, T value, [CallerMemberName] string propertyName = null)
{
    if (EqualityComparer<T>.Default.Equals(property, value) == false)
    {
        property = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

2

다음은 NotifyPropertyChanged의 Unity3D 또는 비 CallerMemberName 버전입니다.

public abstract class Bindable : MonoBehaviour, INotifyPropertyChanged
{
    private readonly Dictionary<string, object> _properties = new Dictionary<string, object>();
    private static readonly StackTrace stackTrace = new StackTrace();
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    ///     Resolves a Property's name from a Lambda Expression passed in.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="property"></param>
    /// <returns></returns>
    internal string GetPropertyName<T>(Expression<Func<T>> property)
    {
        var expression = (MemberExpression) property.Body;
        var propertyName = expression.Member.Name;

        Debug.AssertFormat(propertyName != null, "Bindable Property shouldn't be null!");
        return propertyName;
    }

    #region Notification Handlers

    /// <summary>
    ///     Notify's all other objects listening that a value has changed for nominated propertyName
    /// </summary>
    /// <param name="propertyName"></param>
    internal void NotifyOfPropertyChange(string propertyName)
    {
        OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }

    /// <summary>
    ///     Notifies subscribers of the property change.
    /// </summary>
    /// <typeparam name="TProperty">The type of the property.</typeparam>
    /// <param name="property">The property expression.</param>
    internal void NotifyOfPropertyChange<TProperty>(Expression<Func<TProperty>> property)
    {
        var propertyName = GetPropertyName(property);
        NotifyOfPropertyChange(propertyName);
    }

    /// <summary>
    ///     Raises the <see cref="PropertyChanged" /> event directly.
    /// </summary>
    /// <param name="e">The <see cref="PropertyChangedEventArgs" /> instance containing the event data.</param>
    internal void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, e);
        }
    }

    #endregion

    #region Getters

    /// <summary>
    ///     Gets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <returns></returns>
    internal T Get<T>(Expression<Func<T>> property)
    {
        var propertyName = GetPropertyName(property);
        return Get<T>(GetPropertyName(property));
    }

    /// <summary>
    ///     Gets the value of a property automatically based on its caller.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    internal T Get<T>()
    {
        var name = stackTrace.GetFrame(1).GetMethod().Name.Substring(4); // strips the set_ from name;
        return Get<T>(name);
    }

    /// <summary>
    ///     Gets the name of a property based on a string.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <returns></returns>
    internal T Get<T>(string name)
    {
        object value = null;
        if (_properties.TryGetValue(name, out value))
            return value == null ? default(T) : (T) value;
        return default(T);
    }

    #endregion

    #region Setters

    /// <summary>
    ///     Sets the value of a property whilst automatically looking up its caller name.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    internal void Set<T>(T value)
    {
        var propertyName = stackTrace.GetFrame(1).GetMethod().Name.Substring(4); // strips the set_ from name;
        Set(value, propertyName);
    }

    /// <summary>
    ///     Sets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <param name="name"></param>
    internal void Set<T>(T value, string propertyName)
    {
        Debug.Assert(propertyName != null, "name != null");
        if (Equals(value, Get<T>(propertyName)))
            return;
        _properties[propertyName] = value;
        NotifyOfPropertyChange(propertyName);
    }

    /// <summary>
    ///     Sets the value of a property based off an Expression (()=>FieldName)
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <param name="property"></param>
    internal void Set<T>(T value, Expression<Func<T>> property)
    {
        var propertyName = GetPropertyName(property);

        Debug.Assert(propertyName != null, "name != null");

        if (Equals(value, Get<T>(propertyName)))
            return;
        _properties[propertyName] = value;
        NotifyOfPropertyChange(propertyName);
    }

    #endregion
}

이 코드를 사용하면 다음과 같은 속성 지원 필드를 작성할 수 있습니다.

  public string Text
    {
        get { return Get<string>(); }
        set { Set(value); }
    }

또한 패턴 / 검색 스 니펫을 만들면 간단한 소품 필드를 위의 배경으로 변환하여 작업 흐름을 자동화 할 수 있습니다.

검색 패턴 :

public $type$ $fname$ { get; set; }

패턴 교체 :

public $type$ $fname$
{
    get { return Get<$type$>(); }
    set { Set(value); }
}

2

이것에 도움이되는 기사를 작성했습니다 ( https://msdn.microsoft.com/magazine/mt736453 ). SolSoft.DataBinding NuGet 패키지를 사용할 수 있습니다. 그런 다음 다음과 같은 코드를 작성할 수 있습니다.

public class TestViewModel : IRaisePropertyChanged
{
  public TestViewModel()
  {
    this.m_nameProperty = new NotifyProperty<string>(this, nameof(Name), null);
  }

  private readonly NotifyProperty<string> m_nameProperty;
  public string Name
  {
    get
    {
      return m_nameProperty.Value;
    }
    set
    {
      m_nameProperty.SetValue(value);
    }
  }

  // Plus implement IRaisePropertyChanged (or extend BaseViewModel)
}

혜택:

  1. 기본 수업은 선택 사항입니다
  2. 모든 '설정 값'에 반영되지 않음
  3. 다른 속성에 의존하는 속성을 가질 수 있으며 모두 자동으로 적절한 이벤트를 발생시킵니다 (문서에 이에 대한 예가 있음).

2

AOP 매직 답변을 제외 하고는이를 수행하는 방법이 분명히 많이 있지만, 로컬 필드를 참조하지 않고 뷰 모델에서 직접 모델 속성을 설정하는 것으로 보이는 답변은 없습니다.

문제는 속성을 참조 할 수 없다는 것입니다. 그러나 동작을 사용하여 해당 속성을 설정할 수 있습니다.

protected bool TrySetProperty<T>(Action<T> property, T newValue, T oldValue, [CallerMemberName] string propertyName = null)
{
    if (EqualityComparer<T>.Default.Equals(oldValue, newValue))
    {
        return false;
    }

    property(newValue);
    RaisePropertyChanged(propertyName);
    return true;
}

다음 코드 추출과 같이 사용할 수 있습니다.

public int Prop {
    get => model.Prop;
    set => TrySetProperty(x => model.Prop = x, value, model.Prop);
}

LINQ를 사용하는 방법과 리플렉션을 사용하는 방법을 포함하여 방법의 전체 구현 및 동일한 결과를 달성하는 몇 가지 다른 방법에 대해서는 이 BitBucket 저장소 를 확인하십시오 . 이러한 방법은 성능이 느리다는 점에 유의하십시오.


1

이러한 종류의 속성을 구현할 때 고려해야 할 다른 사항은 INotifyPropertyChang * ed * ing 둘 다 이벤트 인수 클래스를 사용한다는 사실입니다.

많은 수의 속성을 설정하는 경우 이벤트 인수 클래스 인스턴스의 수가 많을 수 있으므로 문자열 폭발이 발생할 수있는 영역 중 하나이므로 캐싱을 고려해야합니다.

이 구현을 살펴보고 왜 구현되었는지 설명하십시오.

조쉬 스미스 블로그


1

방금 ActiveSharp-Automatic INotifyPropertyChanged를 찾았 지만 아직 사용하지 않았지만 좋아 보입니다.

웹 사이트에서 인용하자면 ...


속성 이름을 문자열로 지정하지 않고 속성 변경 알림을 보냅니다.

대신 다음과 같은 속성을 작성하십시오.

public int Foo
{
    get { return _foo; }
    set { SetValue(ref _foo, value); }  // <-- no property name here
}

속성 이름을 문자열로 포함 할 필요는 없습니다. ActiveSharp는이를 확실하고 정확하게 파악합니다. 그것은 속성 구현이 ref로 백킹 필드 (_foo)를 전달한다는 사실을 기반으로 작동합니다. ActiveSharp는이 "ref에 의한"호출을 사용하여 전달 된 지원 필드와 속성을 식별하는 필드를 식별합니다.


1

리플렉션을 사용하는 아이디어 :

class ViewModelBase : INotifyPropertyChanged {

    public event PropertyChangedEventHandler PropertyChanged;

    bool Notify<T>(MethodBase mb, ref T oldValue, T newValue) {

        // Get Name of Property
        string name = mb.Name.Substring(4);

        // Detect Change
        bool changed = EqualityComparer<T>.Default.Equals(oldValue, newValue);

        // Return if no change
        if (!changed) return false;

        // Update value
        oldValue = newValue;

        // Raise Event
        if (PropertyChanged != null) {
            PropertyChanged(this, new PropertyChangedEventArgs(name));
        }//if

        // Notify caller of change
        return true;

    }//method

    string name;

    public string Name {
        get { return name; }
        set {
            Notify(MethodInfo.GetCurrentMethod(), ref this.name, value);
        }
    }//method

}//class

이것은 꽤 멋지다. 나는 표현 방식보다 그것을 좋아한다. 단점은 느려 야합니다.
nawfal

1

나는이 질문에 이미 gazillion 답변이 있음을 알고 있지만 그중 누구도 나에게 맞는 느낌은 없습니다. 내 문제는 성능 저하를 원하지 않으며 그 이유만으로도 약간의 자세를 기꺼이 참을 수 있다는 것입니다. 또한 자동 속성에 너무 신경 쓰지 않아 다음 솔루션으로 이어졌습니다.

public abstract class AbstractObject : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual bool SetValue<TKind>(ref TKind Source, TKind NewValue, params string[] Notify)
    {
        //Set value if the new value is different from the old
        if (!Source.Equals(NewValue))
        {
            Source = NewValue;

            //Notify all applicable properties
            foreach (var i in Notify)
                OnPropertyChanged(i);

            return true;
        }

        return false;
    }

    public AbstractObject()
    {
    }
}

다시 말해, 위의 솔루션은 이것을 원하지 않으면 편리합니다.

public class SomeObject : AbstractObject
{
    public string AnotherProperty
    {
        get
        {
            return someProperty ? "Car" : "Plane";
        }
    }

    bool someProperty = false;
    public bool SomeProperty
    {
        get
        {
            return someProperty;
        }
        set
        {
            SetValue(ref someProperty, value, "SomeProperty", "AnotherProperty");
        }
    }

    public SomeObject() : base()
    {
    }
}

찬성

  • 반사 없음
  • 이전 값! = 새 값인 경우에만 알림
  • 여러 속성을 한 번에 알림

단점

  • 자동 속성이 없습니다 (둘 다 지원을 추가 할 수 있습니다).
  • 일부 상세
  • 복싱 (작은 성능 히트?)

아아, 여전히이 일을하는 것보다 낫습니다.

set
{
    if (!someProperty.Equals(value))
    {
        someProperty = value;
        OnPropertyChanged("SomeProperty");
        OnPropertyChanged("AnotherProperty");
    }
}

추가적인 속성으로 악몽이되는 모든 단일 속성에 대해 ;-(

이 솔루션이 다른 솔루션에 비해 성능면에서 더 우수하다고 주장하지는 않습니다. 제시된 다른 솔루션이 마음에 들지 않는 사람들에게 적합한 솔루션이라고 생각합니다.


1

나는 관찰 가능한 패턴을 구현하기 위해이 기본 클래스를 생각해 냈고, 필요한 것 ( 세트를 자동으로 구현하고 가져 오기)을 거의 수행했습니다. 프로토 타입으로 한 시간을 보냈으므로 많은 단위 테스트가 없지만 개념을 증명합니다. Dictionary<string, ObservablePropertyContext>개인 필드의 필요성을 제거하기 위해를 사용 합니다.

  public class ObservableByTracking<T> : IObservable<T>
  {
    private readonly Dictionary<string, ObservablePropertyContext> _expando;
    private bool _isDirty;

    public ObservableByTracking()
    {
      _expando = new Dictionary<string, ObservablePropertyContext>();

      var properties = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).ToList();
      foreach (var property in properties)
      {
        var valueContext = new ObservablePropertyContext(property.Name, property.PropertyType)
        {
          Value = GetDefault(property.PropertyType)
        };

        _expando[BuildKey(valueContext)] = valueContext;
      }
    }

    protected void SetValue<T>(Expression<Func<T>> expression, T value)
    {
      var keyContext = GetKeyContext(expression);
      var key = BuildKey(keyContext.PropertyName, keyContext.PropertyType);

      if (!_expando.ContainsKey(key))
      {
        throw new Exception($"Object doesn't contain {keyContext.PropertyName} property.");
      }

      var originalValue = (T)_expando[key].Value;
      if (EqualityComparer<T>.Default.Equals(originalValue, value))
      {
        return;
      }

      _expando[key].Value = value;
      _isDirty = true;
    }

    protected T GetValue<T>(Expression<Func<T>> expression)
    {
      var keyContext = GetKeyContext(expression);
      var key = BuildKey(keyContext.PropertyName, keyContext.PropertyType);

      if (!_expando.ContainsKey(key))
      {
        throw new Exception($"Object doesn't contain {keyContext.PropertyName} property.");
      }

      var value = _expando[key].Value;
      return (T)value;
    }

    private KeyContext GetKeyContext<T>(Expression<Func<T>> expression)
    {
      var castedExpression = expression.Body as MemberExpression;
      if (castedExpression == null)
      {
        throw new Exception($"Invalid expression.");
      }

      var parameterName = castedExpression.Member.Name;

      var propertyInfo = castedExpression.Member as PropertyInfo;
      if (propertyInfo == null)
      {
        throw new Exception($"Invalid expression.");
      }

      return new KeyContext {PropertyType = propertyInfo.PropertyType, PropertyName = parameterName};
    }

    private static string BuildKey(ObservablePropertyContext observablePropertyContext)
    {
      return $"{observablePropertyContext.Type.Name}.{observablePropertyContext.Name}";
    }

    private static string BuildKey(string parameterName, Type type)
    {
      return $"{type.Name}.{parameterName}";
    }

    private static object GetDefault(Type type)
    {
      if (type.IsValueType)
      {
        return Activator.CreateInstance(type);
      }
      return null;
    }

    public bool IsDirty()
    {
      return _isDirty;
    }

    public void SetPristine()
    {
      _isDirty = false;
    }

    private class KeyContext
    {
      public string PropertyName { get; set; }
      public Type PropertyType { get; set; }
    }
  }

  public interface IObservable<T>
  {
    bool IsDirty();
    void SetPristine();
  }

사용법은 다음과 같습니다

public class ObservableByTrackingTestClass : ObservableByTracking<ObservableByTrackingTestClass>
  {
    public ObservableByTrackingTestClass()
    {
      StringList = new List<string>();
      StringIList = new List<string>();
      NestedCollection = new List<ObservableByTrackingTestClass>();
    }

    public IEnumerable<string> StringList
    {
      get { return GetValue(() => StringList); }
      set { SetValue(() => StringIList, value); }
    }

    public IList<string> StringIList
    {
      get { return GetValue(() => StringIList); }
      set { SetValue(() => StringIList, value); }
    }

    public int IntProperty
    {
      get { return GetValue(() => IntProperty); }
      set { SetValue(() => IntProperty, value); }
    }

    public ObservableByTrackingTestClass NestedChild
    {
      get { return GetValue(() => NestedChild); }
      set { SetValue(() => NestedChild, value); }
    }

    public IList<ObservableByTrackingTestClass> NestedCollection
    {
      get { return GetValue(() => NestedCollection); }
      set { SetValue(() => NestedCollection, value); }
    }

    public string StringProperty
    {
      get { return GetValue(() => StringProperty); }
      set { SetValue(() => StringProperty, value); }
    }
  }

1

ReactiveProperty를 사용하는 것이 좋습니다. Fody를 제외한 가장 짧은 방법입니다.

public class Data : INotifyPropertyChanged
{
    // boiler-plate
    ...
    // props
    private string name;
    public string Name
    {
        get { return name; }
        set { SetField(ref name, value, "Name"); }
    }
}

대신에

public class Data
{
    // Don't need boiler-plate and INotifyPropertyChanged

    // props
    public ReactiveProperty<string> Name { get; } = new ReactiveProperty<string>();
}

( DOCS )


0

또 다른 아이디어 ...

 public class ViewModelBase : INotifyPropertyChanged
{
    private Dictionary<string, object> _propertyStore = new Dictionary<string, object>();
    protected virtual void SetValue<T>(T value, [CallerMemberName] string propertyName="") {
        _propertyStore[propertyName] = value;
        OnPropertyChanged(propertyName);
    }
    protected virtual T GetValue<T>([CallerMemberName] string propertyName = "")
    {
        object ret;
        if (_propertyStore.TryGetValue(propertyName, out ret))
        {
            return (T)ret;
        }
        else
        {
            return default(T);
        }
    }

    //Usage
    //public string SomeProperty {
    //    get { return GetValue<string>();  }
    //    set { SetValue(value); }
    //}

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        var temp = PropertyChanged;
        if (temp != null)
            temp.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

0

=> 여기 에 다음 기능이있는 내 솔루션

 public ResourceStatus Status
 {
     get { return _status; }
     set
     {
         _status = value;
         Notify(Npcea.Status,Npcea.Comments);
     }
 }
  1. 반박 없음
  2. 짧은 표기법
  3. 비즈니스 코드에 마술 문자열이 없습니다.
  4. 응용 프로그램 전체에서 PropertyChangedEventArgs의 재사용 성
  5. 한 문장으로 여러 속성을 통지 할 가능성

0

이것을 사용하십시오

using System;
using System.ComponentModel;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;


public static class ObservableFactory
{
    public static T Create<T>(T target)
    {
        if (!typeof(T).IsInterface)
            throw new ArgumentException("Target should be an interface", "target");

        var proxy = new Observable<T>(target);
        return (T)proxy.GetTransparentProxy();
    }
}

internal class Observable<T> : RealProxy, INotifyPropertyChanged, INotifyPropertyChanging
{
    private readonly T target;

    internal Observable(T target)
        : base(ImplementINotify(typeof(T)))
    {
        this.target = target;
    }

    public override IMessage Invoke(IMessage msg)
    {
        var methodCall = msg as IMethodCallMessage;

        if (methodCall != null)
        {
            return HandleMethodCall(methodCall);
        }

        return null;
    }

    public event PropertyChangingEventHandler PropertyChanging;
    public event PropertyChangedEventHandler PropertyChanged;



    IMessage HandleMethodCall(IMethodCallMessage methodCall)
    {
        var isPropertySetterCall = methodCall.MethodName.StartsWith("set_");
        var propertyName = isPropertySetterCall ? methodCall.MethodName.Substring(4) : null;

        if (isPropertySetterCall)
        {
            OnPropertyChanging(propertyName);
        }

        try
        {
            object methodCalltarget = target;

            if (methodCall.MethodName == "add_PropertyChanged" || methodCall.MethodName == "remove_PropertyChanged"||
                methodCall.MethodName == "add_PropertyChanging" || methodCall.MethodName == "remove_PropertyChanging")
            {
                methodCalltarget = this;
            }

            var result = methodCall.MethodBase.Invoke(methodCalltarget, methodCall.InArgs);

            if (isPropertySetterCall)
            {
                OnPropertyChanged(methodCall.MethodName.Substring(4));
            }

            return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall);
        }
        catch (TargetInvocationException invocationException)
        {
            var exception = invocationException.InnerException;
            return new ReturnMessage(exception, methodCall);
        }
    }

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

    protected virtual void OnPropertyChanging(string propertyName)
    {
        var handler = PropertyChanging;
        if (handler != null) handler(this, new PropertyChangingEventArgs(propertyName));
    }

    public static Type ImplementINotify(Type objectType)
    {
        var tempAssemblyName = new AssemblyName(Guid.NewGuid().ToString());

        var dynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(
            tempAssemblyName, AssemblyBuilderAccess.RunAndCollect);

        var moduleBuilder = dynamicAssembly.DefineDynamicModule(
            tempAssemblyName.Name,
            tempAssemblyName + ".dll");

        var typeBuilder = moduleBuilder.DefineType(
            objectType.FullName, TypeAttributes.Public | TypeAttributes.Interface | TypeAttributes.Abstract);

        typeBuilder.AddInterfaceImplementation(objectType);
        typeBuilder.AddInterfaceImplementation(typeof(INotifyPropertyChanged));
        typeBuilder.AddInterfaceImplementation(typeof(INotifyPropertyChanging));
        var newType = typeBuilder.CreateType();
        return newType;
    }
}

}

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.