C# 使用PostSharp 1.5实现InotifyProperty更改

C# 使用PostSharp 1.5实现InotifyProperty更改,c#,wpf,inotifypropertychanged,postsharp,C#,Wpf,Inotifypropertychanged,Postsharp,我是.NET和WPF的新手,所以我希望我能正确地问这个问题。 我使用的是使用PostSharp 1.5实现的INotifyPropertyChanged: [Serializable, DebuggerNonUserCode, AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class, AllowMultiple = false, Inherited = false), MulticastAttributeUsage(Mu

我是.NET和WPF的新手,所以我希望我能正确地问这个问题。 我使用的是使用PostSharp 1.5实现的INotifyPropertyChanged:

[Serializable, DebuggerNonUserCode, AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class, AllowMultiple = false, Inherited = false),
MulticastAttributeUsage(MulticastTargets.Class, AllowMultiple = false, Inheritance = MulticastInheritance.None, AllowExternalAssemblies = true)]
public sealed class NotifyPropertyChangedAttribute : CompoundAspect
{
    public int AspectPriority { get; set; }

    public override void ProvideAspects(object element, LaosReflectionAspectCollection collection)
    {
        Type targetType = (Type)element;
        collection.AddAspect(targetType, new PropertyChangedAspect { AspectPriority = AspectPriority });
        foreach (var info in targetType.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(pi => pi.GetSetMethod() != null))
        {
            collection.AddAspect(info.GetSetMethod(), new NotifyPropertyChangedAspect(info.Name) { AspectPriority = AspectPriority });
        }
    }
}

[Serializable]
internal sealed class PropertyChangedAspect : CompositionAspect
{
    public override object CreateImplementationObject(InstanceBoundLaosEventArgs eventArgs)
    {
        return new PropertyChangedImpl(eventArgs.Instance);
    }

    public override Type GetPublicInterface(Type containerType)
    {
        return typeof(INotifyPropertyChanged);
    }

    public override CompositionAspectOptions GetOptions()
    {
        return CompositionAspectOptions.GenerateImplementationAccessor;
    }
}

[Serializable]
internal sealed class NotifyPropertyChangedAspect : OnMethodBoundaryAspect
{
    private readonly string _propertyName;

    public NotifyPropertyChangedAspect(string propertyName)
    {
        if (string.IsNullOrEmpty(propertyName)) throw new ArgumentNullException("propertyName");
        _propertyName = propertyName;
    }

    public override void OnEntry(MethodExecutionEventArgs eventArgs)
    {
        var targetType = eventArgs.Instance.GetType();
        var setSetMethod = targetType.GetProperty(_propertyName);
        if (setSetMethod == null) throw new AccessViolationException();
        var oldValue = setSetMethod.GetValue(eventArgs.Instance, null);
        var newValue = eventArgs.GetReadOnlyArgumentArray()[0];
        if (oldValue == newValue) eventArgs.FlowBehavior = FlowBehavior.Return;
    }

    public override void OnSuccess(MethodExecutionEventArgs eventArgs)
    {
        var instance = eventArgs.Instance as IComposed<INotifyPropertyChanged>;
        var imp = instance.GetImplementation(eventArgs.InstanceCredentials) as PropertyChangedImpl;
        imp.OnPropertyChanged(_propertyName);
    }
}

[Serializable]
internal sealed class PropertyChangedImpl : INotifyPropertyChanged
{
    private readonly object _instance;

    public PropertyChangedImpl(object instance)
    {
        if (instance == null) throw new ArgumentNullException("instance");
        _instance = instance;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    internal void OnPropertyChanged(string propertyName)
    {
        if (string.IsNullOrEmpty(propertyName)) throw new ArgumentNullException("propertyName");
        var handler = PropertyChanged as PropertyChangedEventHandler;
        if (handler != null) handler(_instance, new PropertyChangedEventArgs(propertyName));
    }
}
[Serializable,DebuggerNonUserCode,AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class,AllowMultiple=false,Inherited=false),
MulticastAttributeUsage(MulticastTargets.Class,AllowMultiple=false,继承=MulticastHeritance.None,AllowExternalAssemblies=true)]
公共密封类NotifyPropertyChangedAttribute:CompoundSpect
{
公共int AspectPriority{get;set;}
public override void ProvideSpects(对象元素,LaosReflectionAspectCollection集合)
{
类型targetType=(类型)元素;
AddAspect(targetType,新属性ChangedAspect{AspectPriority=AspectPriority});
foreach(targetType.GetProperties(BindingFlags.Public | BindingFlags.Instance)中的var info,其中(pi=>pi.GetSetMethod()!=null))
{
collection.AddAspect(info.GetSetMethod(),new NotifyPropertyChangedAspect(info.Name){AspectPriority=AspectPriority});
}
}
}
[可序列化]
内部密封类PropertyChangedSpect:CompositionSpect
{
公共重写对象CreateImplementationObject(InstanceBoundLaosEventArgs事件参数)
{
返回新的PropertyChangedImpl(eventArgs.Instance);
}
公共重写类型GetPublicInterface(类型containerType)
{
返回类型(INotifyPropertyChanged);
}
公共覆盖组合SpectOptions GetOptions()
{
返回CompositionSpectOptions.GenerateImplementationAccessor;
}
}
[可序列化]
内部密封类NotifyPropertyChangedAspect:OnMethodBoundaryAspect
{
私有只读字符串_propertyName;
公共NotifyPropertyChangedAspect(字符串propertyName)
{
if(string.IsNullOrEmpty(propertyName))抛出新的ArgumentNullException(“propertyName”);
_propertyName=propertyName;
}
公共重写无效OnEntry(MethodExecutionEventArgs eventArgs)
{
var targetType=eventArgs.Instance.GetType();
var setSetMethod=targetType.GetProperty(_propertyName);
如果(setSetMethod==null)抛出新的AccessViolationException();
var oldValue=setSetMethod.GetValue(eventArgs.Instance,null);
var newValue=eventArgs.GetReadOnlyArgumentArray()[0];
如果(oldValue==newValue)eventArgs.FlowBehavior=FlowBehavior.Return;
}
成功时公共覆盖无效(MethodExecutionEventArgs eventArgs)
{
var instance=eventArgs.instance作为IComposed;
var imp=instance.GetImplementation(eventArgs.InstanceCredentials)作为PropertyChangedImpl;
imp.OnPropertyChanged(_propertyName);
}
}
[可序列化]
内部密封类PropertyChangedImpl:INotifyPropertyChanged
{
私有只读对象_实例;
公共属性changedimpl(对象实例)
{
如果(实例==null)抛出新的ArgumentNullException(“实例”);
_实例=实例;
}
公共事件属性更改事件处理程序属性更改;
内部无效OnPropertyChanged(字符串propertyName)
{
if(string.IsNullOrEmpty(propertyName))抛出新的ArgumentNullException(“propertyName”);
var handler=propertychangedventhadler;
if(handler!=null)处理程序(_实例,新PropertyChangedEventArgs(propertyName));
}
}
}

然后我有两个类(user和address)实现了[NotifyPropertyChanged]。
它很好用。但我想要的是,如果子对象发生更改(在我的示例地址中),则父对象会得到通知(在我的示例中是user)。是否可以扩展此代码,使其自动在父对象上创建侦听器,以侦听其子对象中的更改?

我的方法是实现另一个接口,如
INotifyOnChildChanges
,使用与
属性ChangedEventHandler
匹配的单一方法。然后,我将定义另一个方面,将
PropertyChanged
事件连接到此处理程序

此时,实现了
INotifyPropertyChanged
INotifyOnChildChanges
的任何类都会收到子属性更改的通知


我喜欢这个想法,可能需要自己来实施。请注意,我还发现有相当多的情况下,我希望在属性集之外触发
PropertyChanged
(例如,如果属性实际上是一个计算值,并且您已经更改了其中一个组件),因此将对
PropertyChanged
的实际调用封装到基类中可能是最佳的,这似乎是一个非常普遍的想法。

我不确定这在v1.5中是否有效,但在2.0中有效。我只做了基本的测试(它正确地触发了方法),所以使用它的风险自负

/// <summary>
/// Aspect that, when applied to a class, registers to receive notifications when any
/// child properties fire NotifyPropertyChanged.  This requires that the class
/// implements a method OnChildPropertyChanged(Object sender, PropertyChangedEventArgs e). 
/// </summary>
[Serializable]
[MulticastAttributeUsage(MulticastTargets.Class,
    Inheritance = MulticastInheritance.Strict)]
public class OnChildPropertyChangedAttribute : InstanceLevelAspect
{
    [ImportMember("OnChildPropertyChanged", IsRequired = true)]
    public PropertyChangedEventHandler OnChildPropertyChangedMethod;

    private IEnumerable<PropertyInfo> SelectProperties(Type type)
    {
        const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public;
        return from property in type.GetProperties(bindingFlags)
               where property.CanWrite && typeof(INotifyPropertyChanged).IsAssignableFrom(property.PropertyType)
               select property;
    }

    /// <summary>
    /// Method intercepting any call to a property setter.
    /// </summary>
    /// <param name="args">Aspect arguments.</param>
    [OnLocationSetValueAdvice, MethodPointcut("SelectProperties")]
    public void OnPropertySet(LocationInterceptionArgs args)
    {
        if (args.Value == args.GetCurrentValue()) return;

        var current = args.GetCurrentValue() as INotifyPropertyChanged;
        if (current != null)
        {
            current.PropertyChanged -= OnChildPropertyChangedMethod;
        }

        args.ProceedSetValue();

        var newValue = args.Value as INotifyPropertyChanged;
        if (newValue != null)
        {
            newValue.PropertyChanged += OnChildPropertyChangedMethod;
        }
    }
}
这将应用于实现INotifyPropertyChanged的类的所有子属性。如果您想更具选择性,可以添加另一个简单属性(例如[InterestingChild]),并在MethodPointcut中使用该属性的存在


我在上面发现了一个bug。SelectProperties方法应更改为:

private IEnumerable<PropertyInfo> SelectProperties(Type type)
    {
        const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public;
        return from property in type.GetProperties(bindingFlags)
               where typeof(INotifyPropertyChanged).IsAssignableFrom(property.PropertyType)
               select property;
    }
private IEnumerable SelectProperties(类型)
{
const BindingFlags BindingFlags=BindingFlags.Instance | BindingFlags.declareOnly | BindingFlags.Public;
从type.GetProperties(bindingFlags)中的属性返回
其中typeof(INotifyPropertyChanged).IsAssignableFrom(property.PropertyType)
选择属性;
}
以前,它仅在属性具有setter(即使只有私有setter)时才起作用。如果该属性只有一个getter,则不会收到任何通知。请注意,这仍然只提供单一级别的通知(它不会通知您数据库中任何对象的任何更改)
private IEnumerable<PropertyInfo> SelectProperties(Type type)
    {
        const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public;
        return from property in type.GetProperties(bindingFlags)
               where typeof(INotifyPropertyChanged).IsAssignableFrom(property.PropertyType)
               select property;
    }