c# - 使用 PostSharp 1.5 实现 INotifyPropertyChanged

标签 c# wpf inotifypropertychanged postsharp

我是 .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));
    }
}

然后我有几个实现 [NotifyPropertyChanged] 的类(用户和地址)。 它工作正常。但我想要的是,如果子对象发生变化(在我的示例地址中),父对象会得到通知(在我的例子中是用户)。是否可以扩展此代码,使其自动在父对象上创建监听器以监听其子对象的变化?

最佳答案

我不确定这在 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;
        }
    }
}

用法是这样的:

[NotifyPropertyChanged]
[OnChildPropertyChanged]
class WiringListViewModel
{
    public IMainViewModel MainViewModel { get; private set; }

    public WiringListViewModel(IMainViewModel mainViewModel)
    {
        MainViewModel = mainViewModel;
    }

    private void OnChildPropertyChanged(Object sender, PropertyChangedEventArgs e)
    {
        if (sender == MainViewModel)
        {
            Debug.Print("Child is changing!");
        }
    }
}

这将应用于实现 INotifyPropertyChanged 的​​类的所有子属性。如果您想要更具选择性,您可以添加另一个简单的属性(例如 [InterestingChild])并在 MethodPointcut 中使用该属性的存在。


我在上面发现了一个错误。 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;
    }

以前,只有当属性有一个 setter(即使只有一个私有(private) setter)时它才会起作用。如果该属性只有一个 setter/getter ,您将不会收到任何通知。请注意,这仍然只提供单一级别的通知(它不会通知您层次结构中任何对象的任何更改。)您可以通过手动让 OnChildPropertyChanged 的​​每个实现脉冲 OnPropertyChanged with (null) for 来完成这样的事情属性名称,有效地让 child 的任何变化都被认为是 parent 的整体变化。但是,这可能会导致数据绑定(bind)效率低下,因为它可能会导致重新评估所有绑定(bind)的属性。

关于c# - 使用 PostSharp 1.5 实现 INotifyPropertyChanged,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2432667/

相关文章:

c# - 过滤 ListView 未触发

c# - WPF Datagrid 多项选择在拖动时丢失

c# - INotifyPropertyChanged,这在 MVC 等 Web 应用程序中如何工作或如何工作?

c# - 如何使用 C# 获取第 3 方应用程序按钮的句柄?

c# - 为什么每次都遇到RemoteCertificateNameMismatch?

c# - 我如何知道 ComboBox 中的值何时为 "re-selected"?

c# - Entity Framework - 检查属性是否已映射

.net - 在 MVVM 模型中,模型应该实现 INotifyPropertyChanged 接口(interface)吗?

c# - 如何使用对象 C# 过滤数据网格

c# - 如何通过win forms c#将数据发布到网站