プロパティ変更待機

BindableBaseのPropertyChangedイベントでオブジェクトのプロパティ変更をトリガーにして何らかの処理を行うことはよくあるが、一連の処理の途中でプロパティの変更を待機したい場合、PropertyChangedイベントをどう使っていいのかわからない。

・・・
if (hoge.Foo)
{
  // こんな感じで書くとイベントハンドラの解除ができない
  hoge.PropertyChanged += (s, e) =>
  {
    if (e.PropertyName != "Foo")
    {
      return;
    }
    ・・・
  };
  // イベントハンドラを解除できるように独立したメソッドにするとコードの流れが分断されてしまう。
  // 上の書き方でも十分分断されているが・・・
}

だもんで、こんな感じで待機できるものを作ってみた。

・・・
if (hoge.Foo)
{
  await hoge.WaitPropertyChangeAsync<bool>("Foo");
}
public class PropertyChangedAwaitable : BindableBase
{

  private Dictionary<string, TaskCompletionSource<bool>> _propertyChangedNotifiers = new Dictionary<string, TaskCompletionSource<bool>>();

  public Task<T> WaitPropertyChangeAsync<T>(string propertyName)
  {
    lock (_propertyChangedNotifiers)
    {
      TaskCompletionSource<bool> tcs = null;
      if (!_propertyChangedNotifiers.TryGetValue(propertyName, out tcs))
      {
        tcs = new TaskCompletionSource<bool>();
        _propertyChangedNotifiers[propertyName] = tcs;
      }
      // 待機しているTaskの数だけプロパティ取得のコードが実行されてしまうのがいまいち。
      // プロパティ取得は1回だけで、それを待機しているTaskに通知できればいいのだが・・・
      return tcs.Task.ContinueWith(t => (T)GetType().GetRuntimeProperty(propertyName).GetValue(this), CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
    }
  }

  protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
  {
    lock (_propertyChangedNotifiers)
    {
      TaskCompletionSource<bool> tcs = null;
      if (_propertyChangedNotifiers.TryGetValue(propertyName, out tcs))
      {
        _propertyChangedNotifiers.Remove(propertyName);
        tcs.SetResult(true);
      }
    }
    base.OnPropertyChanged(propertyName);
  }

}