当字段依赖于另一个字段时通知 属性 更改的最佳方式
Best way to notify property change when field is depending on another
在没有 set
但 get
取决于其他字段的情况下,在 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 的最佳方法是什么?我想在设置 Item
时调用 OnPropertyChanged("Field");
但如果我有很多字段,代码很快就会变得丑陋且无法维护。
编辑:
我想知道是否有一个 function/method/attribute 是这样工作的:
[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");
}
}
没有内置机制可以使用属性来指示属性之间的这种关系,但是可以创建一个助手 class 来为您做到这一点。
我在这里做了一个非常基本的例子:
[AttributeUsage( AttributeTargets.Property )]
public class DepondsOnAttribute : Attribute
{
public DepondsOnAttribute( string name )
{
Name = name;
}
public string Name { get; }
}
public class PropertyChangedNotifier<T> : 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<string> 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<DepondsOnAttribute>()
.Select( attribute => attribute.Name );
foreach( var dependency in dependsOn )
{
List<string> list;
if( !smPropertyDependencies.TryGetValue( dependency, out list ) )
{
list = new List<string>();
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<string, List<string>> smPropertyDependencies = new Dictionary<string, List<string>>();
private readonly T mOwner;
}
这不是非常可靠(例如,您可以在属性之间创建循环依赖关系,并且 属性 更改会陷入无限递归情况)。它也可以使用一些 .NET 4.5 和 C#6 功能变得更简单,但我将把所有这些留作 reader 的练习。它可能也不能很好地处理继承。
要使用这个 class:
public class Example : INotifyPropertyChanged
{
private MyClass _item;
private PropertyChangedNotifier<Example> _notifier;
public Example()
{
_notifier = new PropertyChangedNotifier<Example>( 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 某处找到的这种方法,而不是 class 中的硬编码名称(如果 属性 的名称更改,它会中断)。
OnPropertyChanged("MyString");
变为 OnPropertyChanged(GetPropertyName(() => MyString));
其中 GetPropertyName
是:
public static string GetPropertyName<T>(Expression<Func<T>> 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 事件的自定义包装器。我们可以定义以下方法,而不是手动调用 OnPropertyChanged(最好在我们稍后将重用的基础 class 内):
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
internal void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected ViewModelPropertyChange SetPropertyValue<T>(ref T property, T value, [CallerMemberName] string propertyName = null)
{
property = value;
OnPropertyChanged(propertyName);
return new ViewModelPropertyChange(this);
}
}
这 class 为我们提供了一种设置给定字段值的方法,而无需提供调用方的名称。
我们还必须定义一个 class 来定义依赖属性(此 class 的实例从 SetPropertyValue 方法返回)。
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,如下所示:
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;
}
}
在没有 set
但 get
取决于其他字段的情况下,在 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 的最佳方法是什么?我想在设置 Item
时调用 OnPropertyChanged("Field");
但如果我有很多字段,代码很快就会变得丑陋且无法维护。
编辑:
我想知道是否有一个 function/method/attribute 是这样工作的:
[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");
}
}
没有内置机制可以使用属性来指示属性之间的这种关系,但是可以创建一个助手 class 来为您做到这一点。
我在这里做了一个非常基本的例子:
[AttributeUsage( AttributeTargets.Property )]
public class DepondsOnAttribute : Attribute
{
public DepondsOnAttribute( string name )
{
Name = name;
}
public string Name { get; }
}
public class PropertyChangedNotifier<T> : 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<string> 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<DepondsOnAttribute>()
.Select( attribute => attribute.Name );
foreach( var dependency in dependsOn )
{
List<string> list;
if( !smPropertyDependencies.TryGetValue( dependency, out list ) )
{
list = new List<string>();
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<string, List<string>> smPropertyDependencies = new Dictionary<string, List<string>>();
private readonly T mOwner;
}
这不是非常可靠(例如,您可以在属性之间创建循环依赖关系,并且 属性 更改会陷入无限递归情况)。它也可以使用一些 .NET 4.5 和 C#6 功能变得更简单,但我将把所有这些留作 reader 的练习。它可能也不能很好地处理继承。
要使用这个 class:
public class Example : INotifyPropertyChanged
{
private MyClass _item;
private PropertyChangedNotifier<Example> _notifier;
public Example()
{
_notifier = new PropertyChangedNotifier<Example>( 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 某处找到的这种方法,而不是 class 中的硬编码名称(如果 属性 的名称更改,它会中断)。
OnPropertyChanged("MyString");
变为 OnPropertyChanged(GetPropertyName(() => MyString));
其中 GetPropertyName
是:
public static string GetPropertyName<T>(Expression<Func<T>> 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 事件的自定义包装器。我们可以定义以下方法,而不是手动调用 OnPropertyChanged(最好在我们稍后将重用的基础 class 内):
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
internal void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected ViewModelPropertyChange SetPropertyValue<T>(ref T property, T value, [CallerMemberName] string propertyName = null)
{
property = value;
OnPropertyChanged(propertyName);
return new ViewModelPropertyChange(this);
}
}
这 class 为我们提供了一种设置给定字段值的方法,而无需提供调用方的名称。
我们还必须定义一个 class 来定义依赖属性(此 class 的实例从 SetPropertyValue 方法返回)。
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,如下所示:
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;
}
}