当字段取决于另一个字段时,通知属性更改的最佳方法

在没有set的情况下,c#通知在项目字段上更改的属性的最佳方式是什么,但取决于其他字段?

例如 :

 public class Example : INotifyPropertyChanged { private MyClass _item; public event PropertyChangedEventHandler PropertyChanged; public MyClass Item { get { return _item; } protected set { _item = value; OnPropertyChanged("Item"); } } public object Field { get { return _item.Field; } } #if !C#6 protected void OnPropertyChanged(string propertyName) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(propertyName)); } } #else protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); // => can be called in a set like this: // public MyClass Item { set { _item = value; OnPropertyChanged();} } // OnPropertyChanged will be raised for "Item" } #endif } 

设置Item时为"Field"提升PropertyChanged的最佳方法是什么? 我想调用OnPropertyChanged("Field"); 设置Item但如果我有很多字段,代码将很快变得丑陋且无法维护。

编辑:

我想知道是否有像这样工作的函数/方法/属性:

 [DependOn(Item)] public object Field { get { return _item.Field; } } 

=>当Item更改时,所有依赖字段将通知已更改的属性。

它存在吗?

一种方法是多次调用OnPropertyChanged

 public MyClass Item { get { return _item; } protected set { _item = value; OnPropertyChanged("Item"); OnPropertyChanged("Field"); } } 

但是,这不是很容易维护。 另一个选择是将setter添加到get-only属性并从另一个属性设置它:

 public MyClass Item { get { return _item; } protected set { _item = value; OnPropertyChanged("Item"); Field = _item.Field; } } public object Field { get { return _field; } private set { _field = value; OnPropertyChanged("Field"); } } 

没有内置机制来使用属性来指示属性之间的这种关系,但是可以创建一个可以为您执行此操作的帮助程序类。

我已经在这里做了一个非常基本的例子:

 [AttributeUsage( AttributeTargets.Property )] public class DepondsOnAttribute : Attribute { public DepondsOnAttribute( string name ) { Name = name; } public string Name { get; } } public class PropertyChangedNotifier : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public PropertyChangedNotifier( T owner ) { mOwner = owner; } public void OnPropertyChanged( string propertyName ) { var handler = PropertyChanged; if( handler != null ) handler( mOwner, new PropertyChangedEventArgs( propertyName ) ); List dependents; if( smPropertyDependencies.TryGetValue( propertyName, out dependents ) ) { foreach( var dependent in dependents ) OnPropertyChanged( dependent ); } } static PropertyChangedNotifier() { foreach( var property in typeof( T ).GetProperties() ) { var dependsOn = property.GetCustomAttributes( true ) .OfType() .Select( attribute => attribute.Name ); foreach( var dependency in dependsOn ) { List list; if( !smPropertyDependencies.TryGetValue( dependency, out list ) ) { list = new List(); smPropertyDependencies.Add( dependency, list ); } if (property.Name == dependency) throw new ApplicationException(String.Format("Property {0} of {1} cannot depends of itself", dependency, typeof(T).ToString())); list.Add( property.Name ); } } } private static readonly Dictionary> smPropertyDependencies = new Dictionary>(); private readonly T mOwner; } 

这不是非常强大(例如,您可以在属性之间创建循环依赖关系,并且更改的属性将陷入无限递归情况)。 使用一些.NET 4.5和C#6function也可以使它变得更简单,但我将把所有这些留作读者的练习。 它可能也不能很好地处理inheritance。

要使用这个类:

 public class Example : INotifyPropertyChanged { private MyClass _item; private PropertyChangedNotifier _notifier; public Example() { _notifier = new PropertyChangedNotifier( this ); } public event PropertyChangedEventHandler PropertyChanged { add { _notifier.PropertyChanged += value; } remove { _notifier.PropertyChanged -= value; } } public MyClass Item { get { return _item; } protected set { _item = value; OnPropertyChanged("Item"); } } [DependsOn( "Item" )] public object Field { get { return _item.Field; } } protected void OnPropertyChanged(string propertyName) { _notifier.OnPropertyChanged( propertyName ); } } 

据我所知,没有内置的方法。 我通常喜欢这样:

 public class Foo : INotifyPropertyChanged { private Bar _bar1; public Bar Item { get { return _bar1; } set { SetField(ref _bar1, value); ItemChanged(); } } public string MyString { get { return _bar1.Item; } } private void ItemChanged() { OnPropertyChanged("MyString"); } } public class Bar { public string Item { get; set; } } 

您没有这种方式在属性中的通知逻辑。 在我看来,这种方式更易于维护,并且很清楚该方法的作用。

此外,我更喜欢使用我在SO上找到的这个方法而不是类中的硬编码名称(如果属性的名称发生更改,则会中断)。

OnPropertyChanged("MyString"); 成为OnPropertyChanged(GetPropertyName(() => MyString));

其中GetPropertyName是:

 public static string GetPropertyName(Expression> propertyLambda) { if (propertyLambda == null) throw new ArgumentNullException("propertyLambda"); var me = propertyLambda.Body as MemberExpression; if (me == null) { throw new ArgumentException("You must pass a lambda of the form: '() => Class.Property' or '() => object.Property'"); } return me.Member.Name; } 

在此之后,每次我将属性更改为名称时,我将不得不在具有GetPropertyName地方重命名该属性,而不是搜索硬编码的字符串值。

我也很好奇有一个内置的方式来做依赖,所以我在那里放一个最喜欢的:)

尽管在这个解决方案中,事件仍然是从setter传播的(所以问题不完全是这样),它为表示依赖关系提供了一种更好,更易于管理的方式。 有人可能觉得它很有用。

解决方案是创建一个自定义包装器来触发INotifyPropertyChanged事件。 我们可以定义以下mathods(最好在我们稍后将重用的基类中),而不是手动调用OnPropertyChanged:

 public abstract class ViewModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; internal void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } protected ViewModelPropertyChange SetPropertyValue(ref T property, T value, [CallerMemberName] string propertyName = null) { property = value; OnPropertyChanged(propertyName); return new ViewModelPropertyChange(this); } } 

这个类为我们提供了一种设置给定字段值的方法,而无需提供调用来自的proparty的名称。

我们还必须定义一个类,它将用于定义依赖属性(此类的实例是从SetPropertyValue mathod返回的)。

 public class ViewModelPropertyChange { private readonly ViewModelBase _viewModel; public ViewModelPropertyChange(ViewModelBase viewModel) { _viewModel = viewModel; } public ViewModelPropertyChange WithDependent(string name) { _viewModel.OnPropertyChanged(name); return this; } } 

它只是存储对正在更改的对象的引用,并且可以将事件传播到下一个属性。

有了这个,我们可以创建一个从ViewModelBase派生的类,如下所示:

 class OurViewModel : ViewModelBase { private int _partOne; public int PartOne { get => _partOne; set => SetPropertyValue(ref _partOne, value) .WithDependent(nameof(Total)); } private int _partTwo; public int PartTwo { get => _partTwo; set => SetPropertyValue(ref _partTwo, value) .WithDependent(nameof(Total)) .WithDependent(nameof(PartTwoPlus2)); } public int Total { get => PartOne + PartTwo; } public int PartTwoPlus2 { get => PartTwo + 2; } }