What is the best way in c# to notify property changed on an item\'s field without set
but get
depends on other fields ?
For example :
As far as I know, there is not built in method for that. I usually do like this:
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; }
}
You don't have the notifying logic inside of the property this way. It is more maintainable in my opinion this way and it is clear what the method does.
Also, I prefer to use this method I found somewhere on SO instead of the hardcoded name in the class (if the name of the property changes, it breaks).
OnPropertyChanged("MyString");
becomes OnPropertyChanged(GetPropertyName(() => MyString));
where GetPropertyName
is:
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;
}
After this, every time I change the property as the name, I will have to rename the property everywhere I have GetPropertyName
instead of searching for the hardcoded string values.
I'm also curious about a built-in way to do the dependency, so I'm putting a favorite in there :)
One way is to just call OnPropertyChanged
multiple times:
public MyClass Item
{
get
{
return _item;
}
protected set
{
_item = value;
OnPropertyChanged("Item");
OnPropertyChanged("Field");
}
}
This isn't very maintainable, however. Another option is to add a setter to your get-only property and set it from the other property:
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");
}
}
There is no built-in mechanism for using attributes to indicate this relationship between properties, however it would be possible to create a helper class that could do it for you.
I've made a really basic example of what that might look like here:
[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;
}
This isn't terribly robust (for example you could create a circular dependency between properties and the property changed would get stuck in an infinite recursion situation). It can also be made simpler using some .NET 4.5 and C#6 features, but I'll leave all that as an exercise for the reader. It probably also doesn't handle inheritance very well.
To use this 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 );
}
}
Even though in this solution the event is still propagated from the setter (so not exactly what the question is about), it provides a nice, more manageable way for representing dependencies. Someone might find it useful.
The solution is to create a custom wrapper for triggering INotifyPropertyChanged events. Instead of calling OnPropertyChanged manually we can define following mathods (preferably inside a base class that we will reuse later):
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);
}
}
This class provides us with a way of setting a value of a given field without a need for providing the name of a proparty the call comes from.
We also have to define a class that will enable use to define dependent properties (instance of this class is returned from 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;
}
}
It simply stores a reference to an object that is being changed and makes it possible to propagate an event to next properties.
With this we can create a class derived from ViewModelBase like this:
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;
}
}