PropertyChangedProxy_ja - Houzkin/TreeStructures GitHub Wiki

PropertyChangedProxy

INotifyPropertyChangedの実装を補助することが目的です。

Member

/// <summary>
/// A proxy class that supports the implementation of <see cref="INotifyPropertyChanged"/>.
/// It helps manage property changes and notifications efficiently.
///</summary>
public class PropertyChangeProxy {

	/// <summary>
	/// Initializes a new instance of the <see cref="PropertyChangeProxy"/> class
	/// that delegates notifications to the specified event handler.
	/// </summary>
	/// <param name="sender">The object that owns this proxy.</param>
	/// <param name="handlerGetter">A function that retrieves the event handler for notifications.</param>
	public PropertyChangeProxy(object? sender, Func<PropertyChangedEventHandler?> handlerGetter) { }

	/// <summary>
	/// Initializes a new instance of the <see cref="PropertyChangeProxy"/> class
	/// that delegates notifications using a specified action.
	/// </summary>
	/// <param name="raiseAction">An action to invoke property change notifications.</param>
	public PropertyChangeProxy(Action<PropertyChangedEventArgs> raiseAction) { }

	/// <summary>
	/// Sets the given storage value and sends a property change notification if the value has changed.
	/// </summary>
	/// <typeparam name="T">The type of the property.</typeparam>
	/// <param name="storage">A reference to the backing field of the property.</param>
	/// <param name="value">The new value to set.</param>
	/// <param name="propertyName">The name of the property (automatically determined).</param>
	/// <returns>A result indicating whether the value was changed.</returns>
	public ResultWithValue<PropertyChangeProxy> TrySetAndNotify<T>(ref T storage, T value, [CallerMemberName] string? propertyName = null) { }

	/// <summary>
	/// Sends a property change notification for the specified property name.
	/// </summary>
	/// <param name="propertyName">The name of the property (automatically determined).</param>
	/// <returns>The current instance of <see cref="PropertyChangeProxy"/>.</returns>
	public PropertyChangeProxy Notify([CallerMemberName] string? propertyName = null) { }

	/// <summary>
	/// Retrieves a cached <see cref="PropertyChangedEventArgs"/> instance for the specified property name.
	/// If no cached instance exists, a new one is created and stored.
	/// </summary>
	/// <param name="name">The name of the property for which to retrieve an event args instance.</param>
	/// <returns>A cached <see cref="PropertyChangedEventArgs"/> instance associated with the given property name.</returns>
	public static PropertyChangedEventArgs GetOrAddCachedEventArgs(string name) { }
}

コンストラクタを二つ用意しています。
使用先のクラスで、継承によるカスタマイズを考慮しないのであれば前者を使用してシンプルに記述できます。
overrideによってカスタマイズすることを考慮するのであれば後者を使用して拡張性を保てます。

Example (カスタマイズの考慮なし)

public class NotifierBase : INotifyPropertyChanged {
	public NotiferBase () {
		PropertyChangeNotifier = new PropertyChangeProxy(this, () => PropertyChanged);
	}
	protected readonly PropertyChangeProxy PropertyChangeNotifier;
	public event PropertyChangedEventHandler? PropertyChanged;
}
public class NotifierPerson : NotifierBase {
	string _firstName = "";
	string _lastName = "";
	public string FirstName {
		get => _fistName;
		set => PropertyChangeNotifier.TrySetAndNotify(ref _firstName, value).When(o => o.Notify(nameof(FullName)));
	}
	public string LastName {
		get => _lastName;
		set => PropertyChangeNotifier.TrySetAndNotify(ref _lastName, value).When(o => o.Notify(nameof(FullName)));
	}
	public string FullName => FistName + " " + LastName;
}

Example (カスタマイズの考慮あり)

public class NotifierBase : INotifyPropertyChanged {
	public NotifierBase() {
		propChangeNotifier = new PropertyChangeProxy(RaisePropertyChangedEvent);
	}
	private readonly PropertyChangeProxy propChangeNotifier;

	protected virtual void RaisePropertyChangedEvent(PropertyChangedEventArgs e) {
		PropertyChanged?.Invoke(this, e);
	}
	public virtual event PropertyChangedEventHandler? PropertyChanged;

	protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName]string? propName = null) 
		=> propChangeNotifier.TrySetAndNotify(ref storage, value, propName);

	protected void OnPropertyChanged([CallerMemberName]string? propName = null)
		=> propChangeNotifier.Notify(propName);

}
public class NotifierPerson : NotifierBase {
	string _firstName = "";
	string _lastName = "";
	public string FirstName {
		get => _fistName;
		set {
			if (SetProperty(ref _firstName, value)) OnPropertyChanged(nameof(FullName));
		}
	}
	public string LastName {
		get => _lastName;
		set {
			if (SetProperty(ref _lastName, value)) OnPropertyChanged(nameof(FullName));
		}
	}
	public string FullName => FirstName + " " + LastName;

    // override members
	protected override void RaisePropertyChangedEvent(PropertyChangedEventArgs e) {
		Console.WriteLine("Raise Event.");
		base.RaisePropertyChangedEvent(e);
	}
	public override event PropertyChangedEventHandler? PropertyChanged {
		add {
			base.PropertyChanged += value;
			Console.WriteLine("Handler added.");
		}
		remove {
			base.PropertyChanged -= value;
			Console.WriteLine("Handler removed.");
		}
	}
}

NotifyPropertyChangedProxyを使うメリット

  1. 上記に示した通り、簡潔な記述ができます。
  2. static field でプロパティ名(string)に対するイベント引数をキャッシュとして保持しており、新規インスタンスの初期化を最小限に抑えます。
  3. キャッシュの保持にはConcurrentDictonaryを使用しておりスレッドセーフです。

source code

⚠️ **GitHub.com Fallback** ⚠️