c#在没有set
但get
的项目字段中更改通知属性的最佳方式取决于其他字段?
例如:
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
}
设置"Field"
时,为Item
设置PropertyChanged的最佳方法是什么?我想在设置OnPropertyChanged("Field");
时致电Item
,但如果我有很多字段,代码将很快变得难看并且无法维护。
编辑:
我想知道是否有像这样的函数/方法/属性:
[DependOn(Item)]
public object Field
{
get
{
return _item.Field;
}
}
=>当Item
更改时,所有相关字段都会通知已更改的属性。
它存在吗?
答案 0 :(得分:6)
一种方法是多次拨打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<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功能也可以简化它,但我会将所有这些留作读者的练习。它可能也不能很好地处理继承。
要使用此课程:
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 );
}
}
答案 1 :(得分:2)
据我所知,没有内置的方法。我通常喜欢这样:
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<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
的任何地方重命名该属性,而不是搜索硬编码的字符串值。
我也对内置的依赖方式感到好奇,所以我在那里放了一个喜欢的东西:)
答案 2 :(得分:1)
即使在这个解决方案中,事件仍然是从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<T>(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;
}
}