ProgramingTip

자동으로 INotifyPropertyChanged

bestdevel 2020. 12. 14. 20:37
반응형

자동으로 INotifyPropertyChanged


모든 setter에서 OnPropertyChanged를 작성하지 유료 클래스의 속성 변경에 대한 알림을 자동으로받는 방법이 있습니까? (나는 그들이 변경할 수있는 수백 개의 속성이 있습니다).


Anton은 동적 프록시를 제안 합니다. 나는 실제로 과거에 추가하는 것을 "Castle"라이브러리를 사용하고, 내가 작성해야하는 코드의 양을 줄이면서도 내 프로그램 시작 시간 (ymmv)에 약 30 초가 추가되었습니다. 작동 솔루션.

타임 솔루션이 있는지 궁금합니다. 타임 속성을 사용할 수 있습니다.


Slashene과 TcK는 반복적 인 코드를 생성하는 제안을 제공합니다. 불행히도 모든 속성이 m_Value = value의 단순한 경우는 아닙니다. 많은 속성이 setter에 사용자 지정 코드를 가지고 있으므로 니펫과 xml의 쿠키 커터 코드는 실제로 실현 가능하지 않습니다. 내 프로젝트도.


편집 : NotifyPropertyWeaver의 작성자는보다 일반적인 Fody 를 위해 도구를 사용하지 않습니다 . ( 위버에서 fody로 이동하는 사람들을위한 마이그레이션 가이드 를 사용할 수 있습니다.)


제가 프로젝트에 매우 편리한 도구는 Notify Property Weaver Fody 입니다.

에서 빌드-step 프로젝트로 자체 설치되고 컴파일 중에 PropertyChanged이벤트를 발생 시키는 코드를 삽입합니다 .

속성이 PropertyChanged를 올리도록 만드는 것은 특수 속성 을 추가하여 수행 합니다.

[ImplementPropertyChanged]
public string MyProperty { get; set; }

개체로 다른 속성에 의존하는 속성에 대한 관계를 가질 수도 있습니다.

[ImplementPropertyChanged]
public double Radius { get; set; }

[DependsOn("Radius")]
public double Area 
{
    get { return Radius * Radius * Math.PI; }
}

nameof 연산자는 2015 년 7 월에 .NET 4.6 및 VS2015와 함께 C # 6.0에서 구현되었습니다. 다음은 C # <6.0에 대해 여전히 유효합니다.

아래 코드를 사용합니다 ( http://www.ingebrigtsen.info/post/2008/12/11/INotifyPropertyChanged-revisited.aspx에서 ). 잘 작동합니다 :)

public static class NotificationExtensions
{
    #region Delegates

    /// <summary>
    /// A property changed handler without the property name.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="sender">The object that raised the event.</param>
    public delegate void PropertyChangedHandler<TSender>(TSender sender);

    #endregion

    /// <summary>
    /// Notifies listeners about a change.
    /// </summary>
    /// <param name="EventHandler">The event to raise.</param>
    /// <param name="Property">The property that changed.</param>
    public static void Notify(this PropertyChangedEventHandler EventHandler, Expression<Func<object>> Property)
    {
        // Check for null
        if (EventHandler == null)
            return;

        // Get property name
        var lambda = Property as LambdaExpression;
        MemberExpression memberExpression;
        if (lambda.Body is UnaryExpression)
        {
            var unaryExpression = lambda.Body as UnaryExpression;
            memberExpression = unaryExpression.Operand as MemberExpression;
        }
        else
        {
            memberExpression = lambda.Body as MemberExpression;
        }

        ConstantExpression constantExpression;
        if (memberExpression.Expression is UnaryExpression)
        {
            var unaryExpression = memberExpression.Expression as UnaryExpression;
            constantExpression = unaryExpression.Operand as ConstantExpression;
        }
        else
        {
            constantExpression = memberExpression.Expression as ConstantExpression;
        }

        var propertyInfo = memberExpression.Member as PropertyInfo;

        // Invoke event
        foreach (Delegate del in EventHandler.GetInvocationList())
        {
            del.DynamicInvoke(new[]
            {
                constantExpression.Value, new PropertyChangedEventArgs(propertyInfo.Name)
            });
        }
    }


    /// <summary>
    /// Subscribe to changes in an object implementing INotifiyPropertyChanged.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="ObjectThatNotifies">The object you are interested in.</param>
    /// <param name="Property">The property you are interested in.</param>
    /// <param name="Handler">The delegate that will handle the event.</param>
    public static void SubscribeToChange<T>(this T ObjectThatNotifies, Expression<Func<object>> Property, PropertyChangedHandler<T> Handler) where T : INotifyPropertyChanged
    {
        // Add a new PropertyChangedEventHandler
        ObjectThatNotifies.PropertyChanged += (s, e) =>
            {
                // Get name of Property
                var lambda = Property as LambdaExpression;
                MemberExpression memberExpression;
                if (lambda.Body is UnaryExpression)
                {
                    var unaryExpression = lambda.Body as UnaryExpression;
                    memberExpression = unaryExpression.Operand as MemberExpression;
                }
                else
                {
                    memberExpression = lambda.Body as MemberExpression;
                }
                var propertyInfo = memberExpression.Member as PropertyInfo;

                // Notify handler if PropertyName is the one we were interested in
                if (e.PropertyName.Equals(propertyInfo.Name))
                {
                    Handler(ObjectThatNotifies);
                }
            };
    }
}

예를 들어 다음과 같이 사용됩니다.

public class Employee : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private string _firstName;
    public string FirstName
    {
        get { return this._firstName; }
        set
        {
            this._firstName = value;
            this.PropertyChanged.Notify(()=>this.FirstName);
        }
    }
}

private void firstName_PropertyChanged(Employee sender)
{
    Console.WriteLine(sender.FirstName);
}

employee = new Employee();
employee.SubscribeToChange(() => employee.FirstName, firstName_PropertyChanged);

예제에 일부 구문 오류가 있습니다. 테스트하지 않았습니다. 그러나 거기에 개념이 있어야합니다 :)

편집 : 이제 더 많은 작업을 원할 것 같기도하고 ... 위의 내용은 훨씬 쉽게 만듭니다. 그리고 제거 할 때 사용하여 속성을 참조 할 때 발생하는 모든 무서운 문제를 방지 할 수 있습니다.


Framework 4.5는 사용 가능한 속성 이름을 제공받을 수 없습니다.CallerMemberNameAttribute

private string m_myProperty;
public string MyProperty
{
    get { return m_myProperty; }
    set
    {
        m_myProperty = value;
        OnPropertyChanged();
    }
}

private void OnPropertyChanged([CallerMemberName] string propertyName = "none passed")
{
    // ... do stuff here ...
}

Svish의 솔루션과 유사하게 람다의 굉장함을 지루한 프레임 워크 기능으로 대체합니다 ;-)

KB2468871이 동일한 Framework 4.0에서 작업 하는 경우 속성도 제공하는 nuget을 통해 Microsoft BCL 호환 기능 팩사용할 수 있습니다 .


PropertyChanged 대리자에 확장 메소드를 사용할 수 있습니다.

public string Name
{
    get { return name; }
    set
    {
        name = value;
        PropertyChanged.Raise(() => Name);
    }
}

특정 속성 변경에 대한 구독 :

var obj = new Employee();

var handler = obj.SubscribeToPropertyChanged(
    o => o.FirstName, 
    o => Console.WriteLine("FirstName is now '{0}'", o.FirstName));

obj.FirstName = "abc";

// Unsubscribe when required
obj.PropertyChanged -= handler;

확장 방법은 람다 식 트리를 검사하는 것만으로 성능에 큰 영향을 미치며 보낸 사람과 속성 이름을 확인할 수 있습니다 .

public static class PropertyChangedExtensions
{
    public static void Raise<TProperty>(
        this PropertyChangedEventHandler handler, Expression<Func<TProperty>> property)
    {
        if (handler == null)
            return;

        var memberExpr = (MemberExpression)property.Body;
        var propertyName = memberExpr.Member.Name;
        var sender = ((ConstantExpression)memberExpr.Expression).Value;
        handler.Invoke(sender, new PropertyChangedEventArgs(propertyName));
    }

    public static PropertyChangedEventHandler SubscribeToPropertyChanged<T, TProperty>(
        this T obj, Expression<Func<T, TProperty>> property, Action<T> handler)
        where T : INotifyPropertyChanged
    {
        if (handler == null)
            return null;

        var memberExpr = (MemberExpression)property.Body;
        var propertyName = memberExpr.Member.Name;

        PropertyChangedEventHandler subscription = (sender, eventArgs) =>
        {
            if (propertyName == eventArgs.PropertyName)
                handler(obj);
        };

        obj.PropertyChanged += subscription;

        return subscription;
    }
}

경우 PropertyChanged이벤트는 기본 유형에 선언 다음은 파생 클래스에 위임 필드로 표시됩니다. 이 경우 해결 방법은 보호 된 필드 유형을 선언하고 PropertyChangedEventHandler이벤트 addremove접근 자를 명시 적으로 구현하는 것입니다 .

public class Base : INotifyPropertyChanged
{
    protected PropertyChangedEventHandler propertyChanged;
    public event PropertyChangedEventHandler PropertyChanged
    {
        add { propertyChanged += value; }
        remove { propertyChanged -= value; }
    }
}

public class Derived : Base
{
    string name;

    public string Name
    {
        get { return name; }
        set
        {
            name = value;
            propertyChanged.Raise(() => Name);
        }
    }
}


형식 안전 구현 INotifyPropertyChanged: 여기를 참조하십시오.

그런 다음 자신의 코드 스 니펫을 만드십시오.

private $Type$ _$PropertyName$;
public $Type$ $PropertyName$
{
    get
    {
        return _$PropertyName$;
    }
    set
    {
        if(value != _$PropertyName$)
        {
            _$PropertyName$ = value;
            OnPropertyChanged(o => o.$PropertyName$);               
        }
    }
}

으로 코드 디자이너를 니펫을 당신이 짓을! .NET Framework를 구축하는 제부 안전한 방법 INotifyPropertyChanged입니다.


표준 방법은 모르지만 두 가지 해결 방법을 알고 있습니다.

1) PostSharp 는 이것을 수행 할 수 있습니다. 매우 유용하지만 모든 빌드에 시간이 있습니다.

2) 사용자 지정 도구 i Visual Studio. "부분 클래스"와 결합 할 수 있습니다. 그런 다음 XML 용 사용자 지정 도구를 만들고 xml에서 소스 코드를 생성 할 수 있습니다.

예를 들어 다음 xml :

<type scope="public" type="class" name="MyClass">
    <property scope="public" type="string" modifier="virtual" name="Text" notify="true" />
</type>

이 코드의 소스가 될 수 있습니다.

public partial class MyClass {
    private string _text;
    public virtual string Text {
        get { return this._Text; }
        set {
            this.OnPropertyChanging( "Text" );
            this._Text = value;
            this.OnPropertyChanged( "Text" );
        }
    }
}

Aspect-Oriented Programming을 전체적으로보고 싶을 수도 있습니다.

프레임 워크 => linfu를 볼 수 있습니다.


Castle 또는 Spring.NET을 구현할 수 있습니까?


방금 ActiveSharp - 자동에서 INotifyPropertyChanged를 찾았 지만 아직 사용하지 않았지만 괜찮아 보입니다.

웹 사이트에서 인용 비용 ...


속성 이름을 변경하기 위해 지정하지 않고 속성 변경 알림을 보냅니다.

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

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

속성 이름을 공유 할 수 없습니다. ActiveSharp는 안정을 제공합니다. 속성 구현이 ref에 의해 지원 필드 (_foo)를 전달한다는 사실을 기반으로 작동합니다. (ActiveSharp는 "by ref"호출을 사용하여 전달 된 지원 필드를 포토하고 필드에서 속성을 이미지합니다.)


어린이 수업에서 이벤트 호출 개선 :

덕분에 호출 : this.NotifyPropertyChange (() => PageIndex);

NotificationExtensions 클래스에 다음을 추가하십시오.

    /// <summary>
    /// <para>Lève l'évènement de changement de valeur sur l'objet <paramref name="sender"/>
    /// pour la propriété utilisée dans la lambda <paramref name="property"/>.</para>
    /// </summary>
    /// <param name="sender">L'objet portant la propriété et l'évènement.</param>
    /// <param name="property">Une expression lambda utilisant la propriété subissant la modification.</param>
    public static void NotifyPropertyChange(this INotifyPropertyChanged sender, Expression<Func<Object>> property)
    {
        if (sender == null)
            return;

        // Récupère le nom de la propriété utilisée dans la lambda en argument
        LambdaExpression lambda = property as LambdaExpression;
        MemberExpression memberExpression;
        if (lambda.Body is UnaryExpression)
        {
            UnaryExpression unaryExpression = lambda.Body as UnaryExpression;
            memberExpression = unaryExpression.Operand as MemberExpression;
        }
        else
        {
            memberExpression = lambda.Body as MemberExpression;
        }
        ConstantExpression constantExpression = memberExpression.Expression as ConstantExpression;
        PropertyInfo propertyInfo = memberExpression.Member as PropertyInfo;


        // il faut remonter la hierarchie, car meme public, un event n est pas visible dans les enfants
        FieldInfo eventField;
        Type baseType = sender.GetType();
        do
        {
            eventField = baseType.GetField(INotifyPropertyChangedEventFieldName, BindingFlags.Instance | BindingFlags.NonPublic);
            baseType = baseType.BaseType;
        } while (eventField == null);

        // on a trouvé l'event, on peut invoquer tt les delegates liés
        MulticastDelegate eventDelegate = eventField.GetValue(sender) as MulticastDelegate;
        if (eventDelegate == null) return; // l'event n'est bindé à aucun delegate
        foreach (Delegate handler in eventDelegate.GetInvocationList())
        {
            handler.Method.Invoke(handler.Target, new Object[] { sender, new PropertyChangedEventArgs(propertyInfo.Name) });
        }
    }

구현을 더 빠르게 하기 위해 니펫을 사용할 수 있습니다.

에서 http://aaron-hoffman.blogspot.it/2010/09/visual-studio-code-snippet-for-notify.html

MV-VM 패턴을 따르는 프로젝트의 ViewModel 클래스에서는 속성의 setter 이벤트 내에서 "PropertyChanged"(INotifyPropertyChanged 인터페이스 구현을 지원하기 위해)를 발생하는 경우가 발생합니다. 이 컴파일러를 서비스로 사용하여 언젠가는 해결 될 지루한 작업입니다.

스 니펫 코어 (저가 아닌 저자에게 전체 크레딧부여됨 )는 다음과 같습니다.

  <Code Language= "csharp "> 
    <![CDATA[public $type$ $property$ 
{ 
    get { return _$property$; } 
    set 
    { 
        if (_$property$ != value) 
        { 
            _$property$ = value; 
            OnPropertyChanged($property$PropertyName); 
        } 
    } 
} 
private $type$ _$property$; 
public const string $property$PropertyName = "$property$";$end$]]> 
</Code> 

사람들이 사용하고자하는 모든 방식을 처리 할 수있는 속성 변경의 단일 구현은 없습니다. 가장 좋은 방법은 도우미 클래스를 생성하여 작업을 수행하는 것입니다. 여기에 제가 사용하는 예제가 있습니다.

/// <summary>
/// Helper Class that automates most of the actions required to implement INotifyPropertyChanged
/// </summary>
public static class HPropertyChanged
{
    private static Dictionary<string, PropertyChangedEventArgs> argslookup = new Dictionary<string, PropertyChangedEventArgs>();
    public static string ThisPropertyName([CallerMemberName]string name = "")
    {
        return name;
    }

    public static string GetPropertyName<T>(Expression<Func<T>> exp)
    {
        string rtn = "";
        MemberExpression mex = exp.Body as MemberExpression;
        if(mex!=null)
            rtn = mex.Member.Name;
        return rtn;
    }

    public static void SetValue<T>(ref T target, T newVal, object sender, PropertyChangedEventHandler handler, params string[] changed)
    {
        if (!target.Equals(newVal))
        {
            target = newVal;
            PropertyChanged(sender, handler, changed);
        }
    }
    public static void SetValue<T>(ref T target, T newVal, Action<PropertyChangedEventArgs> handler, params string[] changed)
    {
        if (!target.Equals(newVal))
        {
            target = newVal;
            foreach (var item in changed)
            {
                handler(GetArg(item));
            }
        }
    }

    public static void PropertyChanged(object sender,PropertyChangedEventHandler handler,params string[] changed)
    {
        if (handler!=null)
        {
            foreach (var prop in changed)
            {
                handler(sender, GetArg(prop));
            }
        }
    }
    public static PropertyChangedEventArgs GetArg(string name)
    {
        if (!argslookup.ContainsKey(name)) argslookup.Add(name, new PropertyChangedEventArgs(name));
        return argslookup[name];
    }
}

편집 : 헬퍼 클래스에서 값 래퍼로 전환하는 것이 제안 그 이후로가 사용하고 있고 작동이 잘 작동합니다.

public class NotifyValue<T>
{
    public static implicit operator T(NotifyValue<T> item)
    {
        return item.Value;
    }

    public NotifyValue(object parent, T value = default(T), PropertyChangingEventHandler changing = null, PropertyChangedEventHandler changed = null, params object[] dependenies)
    {
        _parent = parent;
        _propertyChanged = changed;
        _propertyChanging = changing;

        if (_propertyChanged != null)
        {
            _propertyChangedArg =
                dependenies.OfType<PropertyChangedEventArgs>()
                .Union(
                    from d in dependenies.OfType<string>()
                    select new PropertyChangedEventArgs(d)
                );

        }
        if (_propertyChanging != null)
        {
            _propertyChangingArg =
                dependenies.OfType<PropertyChangingEventArgs>()
                .Union(
                    from d in dependenies.OfType<string>()
                    select new PropertyChangingEventArgs(d)
                );
        }
        _PostChangeActions = dependenies.OfType<Action>();

    }

    private T _Value;

    public T Value
    {
        get { return _Value; }
        set
        {
            SetValue(value);
        }
    }

    public bool SetValue(T value)
    {
        if (!EqualityComparer<T>.Default.Equals(_Value, value))
        {
            OnPropertyChnaging();
            _Value = value;
            OnPropertyChnaged();
            foreach (var action in _PostChangeActions)
            {
                action();
            }
            return true;
        }
        else
            return false;
    }

    private void OnPropertyChnaged()
    {
        var handler = _propertyChanged;
        if (handler != null)
        {
            foreach (var arg in _propertyChangedArg)
            {
                handler(_parent, arg);
            }           
        }
    }

    private void OnPropertyChnaging()
    {
        var handler = _propertyChanging;
        if(handler!=null)
        {
            foreach (var arg in _propertyChangingArg)
            {
                handler(_parent, arg);
            }
        }
    }

    private object _parent;
    private PropertyChangedEventHandler _propertyChanged;
    private PropertyChangingEventHandler _propertyChanging;
    private IEnumerable<PropertyChangedEventArgs> _propertyChangedArg;
    private IEnumerable<PropertyChangingEventArgs> _propertyChangingArg;
    private IEnumerable<Action> _PostChangeActions;
}

사용 예

private NotifyValue<int> _val;
public const string ValueProperty = "Value";
public int Value
{
    get { return _val.Value; }
    set { _val.Value = value; }
}

그런 다음 생성자에서

_val = new NotifyValue<int>(this,0,PropertyChanged,PropertyChanging,ValueProperty );

자동 속성 선언 위에이 속성을 사용하십시오.

[NotifyParentProperty(true)]
public object YourProperty { get; set; }

참고 URL : https://stackoverflow.com/questions/527602/automatically-inotifypropertychanged

반응형