4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ReactiveExtensionsで前回値と今回値を同時に購読

Posted at

前回値も同時に欲しい

値変更時、Subscribeには変更後の値しか流れてきません。しかし前回値も使って比較処理や後片付け処理を実行したい場合があります。そんなときはPairwiseメソッドが利用できます。

public class ViewModel
{
    public ReactiveProperty<int> Num { get; }
    public ViewModel()
    {
        // ReactivePropertyの初期化時にRaiseLatestValueOnSubscribeを設定
        // (初期値を流すように設定)
        Num = new(initialValue: 0, mode: ReactivePropertyMode.RaiseLatestValueOnSubscribe);
        // Pairwiseメソッドで前回値と今回値を流す
        // 初期値が流れてくるときにはまだ値が1つしかないため来ない
        // 次に値が設定されてきたときに前回値と今回値の2つがOldNewPairで流れてくる
        // RaiseLatestValuesOnSubscribe設定がないと初期値が流れてこないため
        // 最初に設定した値はまだ前回値がない状態となり流れてこない
        Num.Pairwise().Subscribe(p => {
            ExecuteOld(p.OldValue); // OldValueから前回値を取得
            ExecuteNew(p.NewValue); // NewValueから今回値を取得
        });
    }
}

OldNewPairを使うのはめんどくさくない?

OldNewPairDeconstructメソッドが定義されておらず分解できません。そこで、こんな拡張メソッドを用意して前回値・今回値に名前をつけて購読処理を実装することができます。

Extensions.cs
public static class Extensions
{
    /// <summary>
    /// <see cref="OldNewPair{T}"/>を2要素に分解します。
    /// </summary>
    /// <typeparam name="T">任意の型</typeparam>
    /// <param name="x">前回値と今回値のペア</param>
    /// <param name="oldItem">前回値</param>
    /// <param name="newItem">今回値</param>
    public static void Deconstruct<T>(this OldNewPair<T> x, out T oldItem, out T newItem)
        => (oldItem, newItem) = (x.OldItem, x.NewItem);
    /// <summary>
    /// 値の変更時に前回値と今回値を同時に購読します。
    /// </summary>
    /// <typeparam name="T">任意の型</typeparam>
    /// <param name="pairwise">前回値と今回値のペア</param>
    /// <param name="onNext">データ受信時の処理</param>
    /// <returns>プロバイダーが通知の送信を完了する前に、オブザーバーが通知の受信を停止できるインターフェイスへの参照。</returns>
    public static IDisposable Subscribe<T>(this IObservable<OldNewPair<T>> pairwise, Action<T, T> onNext)
        => pairwise.Subscribe(p => onNext(p.OldItem, p.NewItem));
    /// <summary>
    /// 値の変更時に前回値と今回値を同時に購読します。エラー発生時は指定のメソッドで処理します。
    /// </summary>
    /// <typeparam name="T">任意の型</typeparam>
    /// <param name="pairwise">前回値と今回値のペア</param>
    /// <param name="onNext">データ受信時の処理</param>
    /// <param name="onError">エラーハンドラー</param>
    /// <returns>プロバイダーが通知の送信を完了する前に、オブザーバーが通知の受信を停止できるインターフェイスへの参照。</returns>
    public static IDisposable Subscribe<T>(this IObservable<OldNewPair<T>> pairwise, Action<T, T> onNext, Action<Exception> onError)
        => pairwise.Subscribe(p => onNext(p.OldItem, p.NewItem), onError);
    /// <summary>
    /// 値の変更時に前回値と今回値を同時に購読します。通知の完了時は所定の処理を行います。
    /// </summary>
    /// <typeparam name="T">任意の型</typeparam>
    /// <param name="pairwise">前回値と今回値のペア</param>
    /// <param name="onNext">データ受信時の処理</param>
    /// <param name="onCompleted">通知完了時の処理</param>
    /// <returns>プロバイダーが通知の送信を完了する前に、オブザーバーが通知の受信を停止できるインターフェイスへの参照。</returns>
    public static IDisposable Subscribe<T>(this IObservable<OldNewPair<T>> pairwise, Action<T, T> onNext, Action onCompleted)
            => pairwise.Subscribe(p => onNext(p.OldItem, p.NewItem), onCompleted);
    /// <summary>
    /// 値の変更時に前回値と今回値を同時に購読します。エラー発生時は指定のメソッドで処理します。通知の完了時は所定の処理を行います。
    /// </summary>
    /// <typeparam name="T">任意の型</typeparam>
    /// <param name="pairwise">前回値と今回値のペア</param>
    /// <param name="onNext">データ受信時の処理</param>
    /// <param name="onError">エラーハンドラー</param>
    /// <param name="onCompleted">通知完了時の処理</param>
    /// <returns>プロバイダーが通知の送信を完了する前に、オブザーバーが通知の受信を停止できるインターフェイスへの参照。</returns>
    public static IDisposable Subscribe<T>(this IObservable<OldNewPair<T>> pairwise,
        Action<T, T> onNext, Action<Exception> onError, Action onCompleted)
        => pairwise.Subscribe(p => onNext(p.OldItem, p.NewItem), onError, onCompleted);
}

こんな感じで使います。

// OldNewPair<T>を分解してAction<T, T>の引数にする
    Num.Pairwise().Subscribe((prev, next) => {
        ExecuteFrom(prev);
        ExecuteTo(next);
        ExecuteWith(prev, next);
    });

この程度のコードだと大した事ないですが、Subscribeの中身が長くなるときには効果的です。もちろんそのような状況では中身をメソッドに切り出せばいいのですが、Subscribe時にしか実行しないようなメソッドなら他のメソッド等からアクセスできる形に切り出したくはないので、少しでも短く書くためにこんな方法もあると思います。

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?