C# 使用PostSharp 1.5实现InotifyProperty更改
我是.NET和WPF的新手,所以我希望我能正确地问这个问题。 我使用的是使用PostSharp 1.5实现的INotifyPropertyChanged: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
[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;
}