【问题标题】:Implementing INotifyPropertyChanged with PostSharp 1.5使用 PostSharp 1.5 实现 INotifyPropertyChanged
【发布时间】:2023-03-29 09:25:01
【问题描述】:

我是 .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] 的类(用户和地址)。 它工作正常。但我想要的是,如果子对象发生变化(在我的示例地址中),父对象会得到通知(在我的情况下是用户)。是否可以扩展此代码,使其自动在父对象上创建侦听器,以侦听其子对象的变化?

【问题讨论】:

  • 您希望孩子听众做什么?
  • 实际上在这一点上我想要的是父母得到通知(关于孩子的任何变化 - 任何深度)。
  • 这是一个更具挑战性的问题。您必须使用某种反思(如果您不能依靠您的孩子来通知您有关他们孩子的变化),并且在反思期间决定如何以及何时递归总是有点冒险。是什么促使您找到这个解决方案?可能会有一些设计更改有助于简化您的任务。
  • 附带说明,如果这篇文章可以标记为 PostSharp,那就太好了。该工具的创建者阅读了这些板,并且能够比我更好地回答您关于 v1.5 的问题。
  • 我确实用 PostSharp 标签标记了这个,我还在 PostSharp 论坛上发帖......现在我正在等待一个 anwser.... 让它工作将是一件大事。放弃所有 NPC 编码并保持课堂清洁是我的梦想成真!

标签: c# wpf inotifypropertychanged postsharp


【解决方案1】:

我不确定这是否适用于 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 时才有效(即使只有私有 setter)。如果该属性只有一个吸气剂,您将不会收到任何通知。请注意,这仍然只提供单级通知(它不会通知您层次结构中任何对象的任何更改。)您可以通过手动让 OnChildPropertyChanged 的​​每个实现脉冲一个 OnPropertyChanged 与 (null) 来完成类似的事情属性名称,有效地让子项的任何更改都被视为父项的整体更改。但是,这可能会导致数据绑定效率低下,因为它可能会导致重新评估所有绑定的属性。

【讨论】:

  • 对不起,但这在 1.5 中不起作用。我缺少 ImportMember 和 MethodPointCut 属性:S
  • 我也在 PostSharp 2.0 上试过这个(但我的主要目标仍然是在 1.5 上做这个)。然而,即使在 2.0 上,我也没有任何成功。父对象上的事件永远不会触发。
  • 我已经在 2.0.我的 IMainViewModel 公开了一个属性 WindowTitle,并且底层类实现了 INotifyPropertyChanged。我在 WiringListViewModel 被实例化后设置了 WindowTitle 的值,我可以看到 Debug 文本打印表明 OnChildPropertyChanged 已被 MainViewModel 调用。
  • 我找不到问题。我创建了一个简单的测试,但 OnChildPropertyChanged 永远不会被调用:S ...如果您有兴趣,我可以将我的测试项目发送给您,以便您帮助我找到错误
  • 请将您的测试项目发送至 dan.bryant@posincorp.com。我现在在我的一个项目中使用这个方面,所以看看是否有我没有发现的失败案例会很好。我同意,使用方面确实有助于保持清洁;现在我可以拥有 WPF 绑定的所有魔力,而无需手动实现通知模式的繁琐(且容易出错)开销。
【解决方案2】:

我解决这个问题的方法是实现另一个接口,比如INotifyOnChildChanges,上面有一个与PropertyChangedEventHandler 匹配的方法。然后,我将定义另一个将PropertyChanged 事件连接到此处理程序的方面。

此时,任何同时实现INotifyPropertyChangedINotifyOnChildChanges 的类都会收到有关子属性更改的通知。

我喜欢这个想法,可能必须自己实施。请注意,我还发现了很多我想在属性集之外触发PropertyChanged 的情况(例如,如果属性实际上是一个计算值并且您更改了其中一个组件),因此将实际调用包装到PropertyChanged 到基类中可能是最佳的。 I use a lambda based solution to ensure type safety,这似乎是一个很常见的想法。

【讨论】:

  • 由于我的长评论不得不将其发布为anwser。如果您找到时间,我会很高兴在您的帮助下实施您的想法...... ofcors :)
猜你喜欢
  • 1970-01-01
  • 2011-04-08
  • 2012-04-05
  • 1970-01-01
  • 1970-01-01
  • 2018-02-14
  • 2010-10-04
  • 2014-06-02
  • 1970-01-01
相关资源
最近更新 更多