Старый вопрос, тем не менее ...
Мой первоначальный подход заключался в том, чтобы прикрепить дочернее свойство, измененное к родительскому. Это имеет преимущество, так как использование родительского события легко. Просто нужно подписаться на родителя.
public class NotifyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
readonly Dictionary<string, AttachedNotifyHandler> attachedHandlers = new Dictionary<string, AttachedNotifyHandler>();
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void AttachPropertyChanged(INotifyPropertyChanged notifyPropertyChanged,
[CallerMemberName] string propertyName = null)
{
if (propertyName == null) throw new ArgumentNullException(nameof(propertyName));
// ReSharper disable once ExplicitCallerInfoArgument
DetachCurrentPropertyChanged(propertyName);
if (notifyPropertyChanged != null)
{
attachedHandlers.Add(propertyName, new AttachedNotifyHandler(propertyName, this, notifyPropertyChanged));
}
}
protected void DetachCurrentPropertyChanged([CallerMemberName] string propertyName = null)
{
if (propertyName == null) throw new ArgumentNullException(nameof(propertyName));
AttachedNotifyHandler handler;
if (attachedHandlers.TryGetValue(propertyName, out handler))
{
handler.Dispose();
attachedHandlers.Remove(propertyName);
}
}
sealed class AttachedNotifyHandler : IDisposable
{
readonly string propertyName;
readonly NotifyChangedBase currentObject;
readonly INotifyPropertyChanged attachedObject;
public AttachedNotifyHandler(
[NotNull] string propertyName,
[NotNull] NotifyChangedBase currentObject,
[NotNull] INotifyPropertyChanged attachedObject)
{
if (propertyName == null) throw new ArgumentNullException(nameof(propertyName));
if (currentObject == null) throw new ArgumentNullException(nameof(currentObject));
if (attachedObject == null) throw new ArgumentNullException(nameof(attachedObject));
this.propertyName = propertyName;
this.currentObject = currentObject;
this.attachedObject = attachedObject;
attachedObject.PropertyChanged += TrackedObjectOnPropertyChanged;
}
public void Dispose()
{
attachedObject.PropertyChanged -= TrackedObjectOnPropertyChanged;
}
void TrackedObjectOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
currentObject.OnPropertyChanged(propertyName);
}
}
}
Использование просто:
public class Foo : NotifyChangedBase
{
Bar bar;
public Bar Bar
{
get { return bar; }
set
{
if (Equals(value, bar)) return;
bar = value;
AttachPropertyChanged(bar);
OnPropertyChanged();
}
}
}
public class Bar : NotifyChangedBase
{
string prop;
public string Prop
{
get { return prop; }
set
{
if (value == prop) return;
prop = value;
OnPropertyChanged();
}
}
}
Однако, этот подход не очень гибок, и его невозможно контролировать, по крайней мере, без дополнительной сложной разработки. Если система подписки обладает гибкостью для обхода вложенных структур данных, ее применимость ограничена дочерними элементами 1-го уровня.
Хотя предостережения могут быть приемлемыми, в зависимости от использования, с тех пор я отошел от этого подхода, так как никогда не был уверен, каким образом будет использоваться структура данных. В настоящее время предпочитают такие решения, как этот:
https://github.com/buunguyen/notify
Таким образом, даже сложные структуры данных просты и предсказуемы, подписчик контролирует, как подписываться и как реагировать, он хорошо работает с возможностями механизмов привязки.