当字段取决于另一个字段时,通知属性更改的最佳方法
在没有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; } }