65.9K
CodeProject 正在变化。 阅读更多。
Home

INotifyPropertyChanged 自动装配, 或如何摆脱冗余代码

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.48/5 (10投票s)

2009年8月10日

Ms-PL

2分钟阅读

viewsIcon

53079

INotifyPropertyChanged 自动装配, 或如何摆脱冗余代码

在过去的一周里,大多数 WPF 门徒都在讨论如何摆脱 INotifyPropertyChanged 实现中硬编码的属性名称字符串,以及如何在继续使用自动属性实现的同时,保持 WPF 绑定正常工作。这个话题是由 Karl Shifflett 启动的,他提出了一种有趣的方法,即使用 StackFrame 来完成这项任务。 在此讨论中,还提出了其他方法,包括代码片段、R#、观察者模式Cinch 框架静态反射弱引用 等。 我也提出了我们类中使用的这种方法,并承诺会写博客文章来介绍它。 因此,今天的主题是如何使用 PostSharp 来自动实现 INotifyPropertyChanged 接口,仅基于自动设置器。

My 5 ¢

因此,我希望我的代码看起来像这样

public class AutoWiredSource { 
   public double MyProperty { get; set; } 
   public double MyOtherProperty { get; set; } 
}

同时能够完全注意到任何属性的任何更改,并使我能够将这些属性绑定到其他元素。

<StackPanel DataContext="{Binding Source={StaticResource source}}"> 
    <Slider Value="{Binding Path=MyProperty}" /> 
    <Slider Value="{Binding Path=MyProperty}" /> 
</StackPanel>

如何实现它? 如何让编译器用以下代码替换我的代码?

private double _MyProperty; 
public double MyProperty { 
   get { return _MyProperty; } 
   set { 
      if (value != _MyProperty) { 
         _MyProperty = value; OnPropertyChanged("MyProperty"); 
      } 
   } 
} 
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(this, new PropertyChangedEventArgs(propertyName)); 
}

很简单:使用面向切面编程将一组指令注入到预编译的源代码中。

首先,我们需要构建一个属性,该属性将用于标记需要更改跟踪的类。 此属性应组合(复合)方面,以包含用于更改跟踪的所有方面。 我们所做的一切就是获取所有 set 方法,并将组合方面添加到

[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; 
   } 
}

接下来,也是最有趣的一个,我们将把它放在方法边界上进行跟踪。 这里有一些要点。 首先,我们不希望在实际值没有更改时触发 PropertyChanged 事件,因此我们将处理方法在其进入和退出时进行检查。

[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); 
   } 
}

我们几乎完成了,我们只需要创建一个实现 INotifyPropertyChanged 的类,并提供一个内部方法来调用。

[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)); 
   } 
}

我们完成了。 最后一步是引用 PostSharp Laos 和 Public 程序集,并标记编译器以使用 Postsharp 目标(在您的项目文件 (*.csproj) 中)。

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> 
<PropertyGroup> 
  <DontImportPostSharp>True</DontImportPostSharp> 
</PropertyGroup> 
<Import Project="PostSharp\PostSharp-1.5.targets" />

现在我们完成了。 我们可以使用清晰的语法,如下所示,使所有具有 public 设置器的属性都可跟踪。 唯一的缺点是您必须将两个 PostSharp 文件与您的项目一起拖动。 但总而言之,它比在整个项目中手动通知更改跟踪要方便得多。

[NotifyPropertyChanged] 
public class AutoWiredSource { 
   public double MyProperty { get; set; } 
}

祝您愉快,做一个好人。 还要尝试思考可以使用 PostSharp(或任何其他面向切面编程引擎)完成的其他极其有用的事情。

相关文章

  1. WPF 开发中的高效工具
  2. 基于触发器的绑定设置
  3. WPF 中自动滚动 ListBox
© . All rights reserved.