it-source

INOTIFY 구현변경된 속성 - 더 나은 방법이 있습니까?

criticalcode 2023. 5. 6. 15:10
반응형

INOTIFY 구현변경된 속성 - 더 나은 방법이 있습니까?

는 Microsoft를 위해 신속한 .INotifyPropertyChanged로 자동속서처럼에, 지정니다합그냥성을 지정하면 .{get; set; notify;}저는 그것을 하는 것이 매우 타당하다고 생각합니다.아니면 그것을 하기 위한 어떤 복잡함이 있습니까?

우리 자신이 부동산에 '알림'과 같은 것을 구현할 수 있습니까?당신의 수업에서 구현하기 위한 우아한 해결책이 있습니까? 아니면 그것을 하는 유일한 방법은 그것을 높이는 것입니다.PropertyChanged각 속성의 이벤트.

않다면, 는 코드 하여 올릴 수 무언가를 쓸 수 있을까요?PropertyChanged 이벤트?

포스트샤프와 같은 것을 사용하지 않고 내가 사용하는 최소 버전은 다음과 같은 것을 사용합니다.

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는 다른 논리를 적용하려는 경우를 대비하여 해당 논리가 no-op인지 여부를 알려줍니다.


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

C# 8 및 Nullable 참조 유형의 경우 다음과 같습니다.

public event PropertyChangedEventHandler? PropertyChanged;

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

protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
{
    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는 발신자 정보 속성을 도입했습니다.

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

마크의 코드에 다음 방법을 추가하기만 하면 효과가 있습니다.

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

그나저나, 이것은 이 블로그 게시물에서 영감을 받았습니다.

추가 기능이 있는 Fody도 있습니다.변경된 속성 알림인터페이스 추가 기능으로 다음과 같이 쓸 수 있습니다.

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

...그리고 컴파일 시 속성 변경된 알림을 주입합니다.

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

여기와 다른 곳에서 발견된 다양한 구현을 비교해 보았습니다. INOTIFY의 성능 비교를 확인하십시오.속성이 구현을 변경했습니다.


입니다.구현 대 런타임

저는 http://timoch.com/blog/2013/08/annoyed-with-inotifypropertychange/ 의 블로그에서 Bindable 클래스를 소개합니다. Bindable은 사전을 속성 가방으로 사용합니다.하위 클래스가 참조 매개 변수를 사용하여 자체 백업 필드를 관리하는 데 필요한 오버로드를 추가하는 것은 매우 쉽습니다.

  • 마법 문자열 없음
  • 무반영
  • 기본 사전 조회를 억제하도록 개선할 수 있습니다.

코드:

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

아직은 직접 시도해 볼 기회가 없었지만, 다음 번에는 INOTIFY에 대한 요구 사항이 큰 프로젝트를 설정할 예정입니다.속성이 변경되었습니다. 컴파일 시 코드를 주입하는 포스트샤프 속성을 작성하려고 합니다.다음과 같은 것:

[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에서 사용자 지정 템플릿을 사용하고 있지만, 그것에도 불구하고 저는 제 모든 속성이 너무 길다는 것에 질렸습니다.


아, 빠른 구글 검색(이 글을 쓰기 전에 했어야 했는데)은 적어도 한 사람이 여기 에 이런 일을 한 적이 있다는 것을 보여줍니다.정확히 제가 생각했던 것은 아니지만, 이론이 좋다는 것을 보여줄 정도로 가깝습니다.

네, 더 좋은 방법이 분명히 존재합니다.여기 있습니다.

이 유용한 글을 바탕으로 나는 차근차근 튜토리얼을 축소했습니다.

  • 새 프로젝트 생성
  • 프로젝트에 캐슬 코어 패키지 설치

설치-패키지 성.코어

  • mvvm light 라이브러리만 설치

설치-패키지 MvmLightLibs

  • 프로젝트에 두 개의 클래스 추가:

통지자요격기

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

프록시 생성기

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

  • 즐거운 시간 되세요.

여기에 이미지 설명 입력

주목!!!제한된 모든 속성은 재정의를 위해 캐슬 프록시에서 사용되므로 키워드 virtual으로 장식되어야 합니다.

매우 AOP와 유사한 접근 방식은 INOTIFY를 주입하는 것입니다.속성 - 이미 인스턴스화된 개체로 항목을 즉시 변경합니다.Castle Dynamic Proxy 같은 것으로 이 작업을 수행할 수 있습니다.다음은 이 기술을 설명하는 기사입니다.

알림 추가속성이 기존 개체로 변경됨

2022년입니다.이제 공식적인 해결책이 있습니다.

Microsoft MVVM Toolkit에서 MVVM 소스 생성기를 사용합니다.

이것.

[ObservableProperty]
private string? name;

다음을 생성합니다.

private string? name;

public string? Name
{
    get => name;
    set
    {
        if (!EqualityComparer<string?>.Default.Equals(name, value))
        {
            OnNameChanging(value);
            OnPropertyChanging();
            name = value;
            OnNameChanged(value);
            OnPropertyChanged();
        }
    }
}

// Property changing / changed listener
partial void OnNameChanging(string? value);
partial void OnNameChanged(string? value);

protected void OnPropertyChanging([CallerMemberName] string? propertyName = null)
{
    PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName));
}

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

를 지원합니다.NET 표준 2.0 및 .NET >= 5.0.

여기를 보세요: 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 ); }
}

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

제 해결책은 코드 스니펫을 사용하여 작업을 수행하는 것입니다.

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

이 스니펫을 저장하고 'fullprop' 스니펫을 사용할 때 사용합니다.

위치는 Visual Studio의 'Tools\Code Snipet 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>

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

저만의 접근법인 '야피'를 소개하겠습니다.Runtime proxy|파생 클래스 생성기에 속하며, 카스트 프로젝트의 동적 프록시와 같은 기존 개체 또는 유형에 새로운 기능을 추가합니다.

INOTIFY를 구현할 수 있습니다.기본 클래스에서 속성이 한 번 변경된 다음 파생 클래스를 다음 스타일로 선언하고 INNotify를 계속 지원합니다.새 속성에 대해 변경된 속성:

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

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

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

그리고 모든 정보를 알려줍니다.PropertyChanged 구현 작업은 다음과 같이 수행할 수 있습니다.

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

이 제품은 리팩토링에 완전히 안전하며, 유형 구성 후 반사가 없으며 충분히 빠릅니다.

이를 위한 방법은 분명히 여러 가지가 있지만, 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);
}

BitBucket repo에서 LINQ를 사용하는 방법과 반사를 사용하는 방법을 포함하여 동일한 결과를 얻을 수 있는 몇 가지 다른 방법을 확인하십시오.이러한 방법은 성능 면에서 더 느립니다.

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

데이터 그리드 보기를 통해 노출된 바인딩 목록에 저장된 항목 모음을 구동합니다.따라서 그리드에 수동으로 새로 고침() 호출을 수행할 필요가 없습니다.

기본 라이브러리에서 재사용할 확장 방법을 만들었습니다.

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

저는 이것을 단편적인 것으로 간직하고 있습니다.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));
    }
}

ActiveSharp - 자동 알림을 방금 찾았습니다.숙박시설이 변경되었습니다. 아직 사용해 본 적은 없지만 좋아 보입니다.

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


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

대신 다음과 같은 속성을 작성합니다.

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

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

에서 동적 기능을 사용하는 경우. 4 INotifyPropertyChanged.

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

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

또 다른 결합 솔루션은 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); }
    }
}

저는 This Way(이것은 약간 힘들지만 런타임에서는 확실히 더 빠름)로 결정했습니다.

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

이 작은 코드는 다음과 같습니다.

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

세트 메소드의 IL 코드를 대체하기 위해 던지지만 IL에 컴파일된 코드를 많이 쓸 수 없습니다...만약 내가 그것을 쓴다면, 나는 당신에게 말할 것입니다!

다음은 Unity3D 또는 호출자가 아닌 구성원 이름 버전의 알림입니다.속성 변경됨

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

이에 도움이 되는 기사를 작성했습니다(https://msdn.microsoft.com/magazine/mt736453) .SolSoft를 사용할 수 있습니다.데이터 바인딩 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. 다른 속성에 의존하는 속성을 가질 수 있으며, 모두 자동으로 해당 이벤트를 발생시킵니다(예: 문서 참조).

저는 관찰 가능한 패턴을 구현하기 위해 이 기본 클래스를 고안했으며, 여러분이 필요로 하는 것을 거의 대부분 수행합니다("세트와 가져오기를 자동으로 구현").프로토타입으로 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); }
    }
  }

이러한 종류의 속성을 구현할 때 고려해야 할 다른 사항은 INOTIFY가PropertyChang *ed *ing은 모두 이벤트 인수 클래스를 사용합니다.

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

이 구현을 살펴보고 이 구현이 구상된 이유에 대한 설명을 살펴봅니다.

조쉬 스미스 블로그

성찰을 이용한 아이디어:

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

사용

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

}

다음 확장 방법(C# 6.0 사용)을 사용하여 INPC를 최대한 쉽게 구현합니다.

public static bool ChangeProperty<T>(this PropertyChangedEventHandler propertyChanged, ref T field, T value, object sender,
    IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null)
{
    if (comparer == null)
        comparer = EqualityComparer<T>.Default;

    if (comparer.Equals(field, value))
    {
        return false;
    }
    else
    {
        field = value;
        propertyChanged?.Invoke(sender, new PropertyChangedEventArgs(propertyName));
        return true;
    }
}

INPC 구현은 다음과 같이 요약됩니다(매번 구현하거나 기본 클래스를 만들 수 있음).

public class INPCBaseClass: INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected bool changeProperty<T>(ref T field, T value,
        IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null)
    {
        return PropertyChanged.ChangeProperty(ref field, value, this, comparer, propertyName);
    }
}

그런 다음 속성을 다음과 같이 작성합니다.

private string testProperty;
public string TestProperty
{
    get { return testProperty; }
    set { changeProperty(ref testProperty, value); }
}

참고: 다음을 생략할 수 있습니다.[CallerMemberName]당신이 원한다면, 확장 방법의 선언이지만, 저는 그것을 유연하게 유지하고 싶었습니다.

은 오버로드할 수 .changeProperty:

protected bool changeProperty<T>(T property, Action<T> set, T value,
    IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null)
{
    bool ret = changeProperty(ref property, value, comparer, propertyName);
    if (ret)
        set(property);
    return ret;
}

사용 예는 다음과 같습니다.

public string MyTestProperty
{
    get { return base.TestProperty; }
    set { changeProperty(base.TestProperty, (x) => { base.TestProperty = x; }, value); }
}

저는 이 질문이 이미 엄청난 답을 가지고 있다는 것을 알지만, 그 중 어느 것도 저에게 적합하다고 생각되지 않습니다.제 문제는 제가 어떤 공연 히트곡도 원하지 않고 그 이유만으로 조금만 더 장황하게 이야기하는 것을 기꺼이 참는다는 것입니다.또한 자동 속성에 대해서도 크게 신경 쓰지 않아 다음과 같은 해결책을 얻었습니다.

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

모든 속성에 대해, 추가 장황함과 함께 악몽이 되는 ;-(

참고로, 저는 이 솔루션이 다른 솔루션에 비해 성능 면에서 더 낫다고 주장하지는 않습니다. 단지 제시된 다른 솔루션을 좋아하지 않는 사람들을 위한 실행 가능한 솔루션이라고 생각합니다.

반응 속성을 사용하는 것이 좋습니다.이것은 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)

언급URL : https://stackoverflow.com/questions/1315621/implementing-inotifypropertychanged-does-a-better-way-exist

반응형