避免在每个setter中调用RaisePropertyChanged

我想摆脱模型类中占用空间和重复性的RaisePropertyChanged-Properties。 我想要我的模特class……

public class ProductWorkItem : NotificationObject { private string name; public string Name { get { return name; } set { if (value == name) return; name = value; RaisePropertyChanged(() => Name); } } private string description; public string Description { get { return description; } set { if (value == description) return; description = value; RaisePropertyChanged(() => Description); } } private string brand; public string Brand { get { return brand; } set { if (value == brand) return; brand = value; RaisePropertyChanged(() => Brand); } } } 

…看起来像这样简单:(但是当属性发生变化时通知视图)

 public class ProductWorkItem { public string Name{ get; set; } public string Description{ get; set; } public string Brand{ get; set; } } 

这可以通过某种代理类来实现吗?

我想避免为每个模型类编写代理。

我知道在“vanilla”C#中没有简单和可维护的方法,但你可以用方面实现这一点。 我已经使用了PostSharp ,它的缺点是作为付费的第三方产品,但有一个免费版本,你也可以这样做。 PostSharp利用目标指定,inheritance等属性的优点,并将它们扩展到方面。

然后,您可以定义LocationInterceptionAspect ,它会覆盖OnSetValue方法以调用您的RaisePropertyChanged委托。 然后,您可以使用使用aspect属性修饰的自动生成的属性。

PostSharp的付费版本允许您在类级别执行此操作,因此您只需要一个属性(如果您装饰基类并将属性定义为可inheritance,则只需要一个属性)。 这在PostSharp站点上描述InstanceLevelAspect的用例

我来到NotifyPropertyWeaver扩展程序并从那时起定期使用它。 它是一个Visual Studio扩展,在编译代码之前为您实现始终相同的INPC内容。 你什么都没注意到。

您需要安装扩展,然后您的模型需要如下所示:

 public class ProductWorkItem : INotifyPropertyChanged { public string Name{ get; set; } public string Description{ get; set; } public string Brand{ get; set; } public event PropertyChangedEventHandler PropertyChanged; } 

扩展比为你添加所有其余的。 我喜欢这种方法的是,你的类仍然“正式”实现INPC接口,你也可以在非WPF上下文中使用它(因为INPC根本不是WPF的东西),但仍然没有用你所有的东西乱丢你的课。 它会针对依赖于属性的只读属性引发通知。

当然,它有点假,因为它只是自动化写作,并没有改变任何有关底层概念的东西。 但也许这是妥协……

以下是更多信息: 链接

我们可以避免在WPF中的每个属性设置器上编写RaisePropertyChanged的重复代码。

使用Postsharp的免费版本。

通过使用以下代码,我们只能将Virtual属性绑定到view。

 namespace Test { [Serializable] [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = true)] public sealed class RaisePropertyChangedAttribute : MethodInterceptionAspect { private string propertyName; ///  /// Compiles the time validate. ///  /// The method. public override bool CompileTimeValidate(MethodBase method) { return IsPropertySetter(method) && !method.IsAbstract && IsVirtualProperty(method); } ///  /// Method invoked at build time to initialize the instance fields of the current aspect. This method is invoked /// before any other build-time method. ///  /// Method to which the current aspect is applied /// Reserved for future usage. public override void CompileTimeInitialize(MethodBase method, AspectInfo aspectInfo) { base.CompileTimeInitialize(method, aspectInfo); propertyName = GetPropertyName(method); } ///  /// Determines whether [is virtual property] [the specified method]. ///  /// The method. ///  /// true if [is virtual property] [the specified method]; otherwise, false. ///  private static bool IsVirtualProperty(MethodBase method) { if (method.IsVirtual) { return true; } var getMethodName = method.Name.Replace("set_", "get_"); var getMethod = method.DeclaringType.GetMethod(getMethodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); return getMethod != null && getMethod.IsVirtual; } private static string GetPropertyName(MethodBase method) { return method.Name.Replace("set_", string.Empty); } ///  /// Determines whether [is property setter] [the specified method]. ///  /// The method. ///  /// true if [is property setter] [the specified method]; otherwise, false. ///  private static bool IsPropertySetter(MethodBase method) { return method.Name.StartsWith("set_", StringComparison.OrdinalIgnoreCase); } ///  /// Method invoked instead of the method to which the aspect has been applied. ///  /// Advice arguments. public override void OnInvoke(MethodInterceptionArgs args) { var arg = args as MethodInterceptionArgsImpl; if ((arg != null) && (arg.TypedBinding == null)) { return; } // Note ViewModelBase is base class for ViewModel var target = args.Instance as ViewModelBase; args.Proceed(); if (target != null) { target.OnPropertyChanged(propertyName); } } } } 

这已经很老了,没人提到:

https://kindofmagic.codeplex.com/

您可以为ViewModel中具有1个属性的每个属性启用自动通知。

我在System.Dynamic命名空间中找到了这个类 …它允许您拦截绑定目标上的DependencyObject对绑定源上的Property进行的实际DataBinding调用。

http://i.msdn.microsoft.com/en-us/library/system.windows.data.binding.DataBindingMostBasic(v=vs.110).png?appId=Dev11IDEF1&l=EN-US&k=k(System.Windows .Data.Binding)%3BK(VS.XamlEditor)%3BK(TargetFrameworkMoniker-.NETFramework

所以现在可以做的是实现一个实现INotifyPropertyChanged的类(让我们称之为DynamicNpcProxy ),它派生自DynamicObject并覆盖TryGetMemberTrySetMember方法。

 public class DynamicNpcProxy : DynamicObject, INotifyPropertyChanged { public DynamicNpcProxy(object proxiedObject) { ProxiedObject = proxiedObject; } //... public object ProxiedObject { get; set; } public override bool TrySetMember(SetMemberBinder binder, object value) { SetMember(binder.Name, value); return true; } protected virtual void SetMember(string propertyName, object value) { GetPropertyInfo(propertyName).SetValue(ProxiedObject, value, null); if (PropertyChanged != null) PropertyChanged(ProxiedObject, new PropertyChangedEventArgs(propertyName)); } protected PropertyInfo GetPropertyInfo(string propertyName) { return ProxiedObject.GetType().GetProperty(propertyName); } // override bool TryGetMember(...) } 

要使其工作,请将代理程序包装在当前绑定源周围,替换它们并让DynamicObject完成剩下的工作。

在ViewModel.cs中:

 IList items; //... assign items var proxies = items.Select(p => new DynamicNpcProxy(p)).ToList(); ICollectionView Products = CollectionViewSource.GetDefaultView(proxies); 

在View.xaml中:

   

你最终得到的是:

还可以在code project查看这篇文章 ,它提供了更多信息……

来自另一方(如果你没有花哨的扩展)你可以使用我的答案中概述的扩展方法“自动指定”已更改的属性: WCF服务代理不设置“FieldSpecified”属性

具体来说,您可以使用“非reflection方法”为每个类配置特定的“OnPropertyChanged”处理,以执行除设置指定字段之外的其他操作。

 public static class PropertySpecifiedExtensions2 { ///  /// Bind the  handler to automatically call each class's  method on the property name. ///  /// the entity to bind the autospecify event to public static void Autonotify(this IAutoNotifyPropertyChanged entity) { entity.PropertyChanged += (me, e) => ((IAutoNotifyPropertyChanged)me).WhenPropertyChanges(e.PropertyName); } ///  /// Create a new entity and  it's properties when changed ///  ///  ///  public static T Create() where T : IAutoNotifyPropertyChanged, new() { var ret = new T(); ret.Autonotify(); return ret; } } ///  /// Used by  to standardize implementation behavior ///  public interface IAutoNotifyPropertyChanged : INotifyPropertyChanged { void WhenPropertyChanges(string propertyName); } 

然后每个类自己定义行为:

 public partial class MyRandomClass: IAutoNotifyPropertyChanged { ///  /// Create a new empty instance and  its properties when changed ///  ///  public static MyRandomClass Create() { return PropertySpecifiedExtensions2.Create(); } public void WhenPropertyChanges(string propertyName) { switch (propertyName) { case "field1": this.field1Specified = true; return; // etc } // etc if(propertyName.StartsWith(...)) { /* do other stuff */ } } } 

当然,这样做的缺点是属性名称的魔术字符串使重构变得困难,你可以通过Expression解析来解决这个问题吗?