別ストリームの現在値を使いたい


Rxで何でもかんでもIObservable<T>で書こうとすると
triggerストリームの発行タイミングで、sourceストリームの最終値を後続に流したい」
ということがよくある。
例えば「ファイルに保存ボタンクリック」イベントストリームの発行タイミングで、「ファイル名」ストリームの現在値を後続に流したい。など。

適当にlatestValueなるフィールドを用意して

source.Subscribe(x => latestValue = x);
trigger.Subscribe(_ => FooBar(latestValue));

こうしてやれば実現できるが、なるべくIObservableでやりたい気分のときどうするか。

Sample()

似たような動きをするのがSample()メソッドであるが、
こいつはsourceが値を発行しない間にtriggerを数発発行しても、最初の一発しかサンプリングしてくれない。

Sample()メソッド:
source   ---1-------2------3------4--
trigger  -------o----o-o--o----o-----
          source.Sample(trigger)
         -------1----2---------3-----

やりたい方:
source   ---1-------2------3------4--
trigger  -------o----o-o--o----o-----
                  ???
         -------1----2-2--2----3-----

CombineLatest()

triggerの分だけ発行してくれるが、sourceの変更でも発行してしまう。

source   ---1-------2--------3------4--
trigger  -------o-----o-o--o-----o-----
          source.CombineLatest(trigger,(s,t)=>s)
         -------1---2-2-2--2-3---3--4--

これではテキストボックスに文字打っただけで保存してしまう。

Sample().CombineLatest()

目的のストリームが得られる。

source   ---1-------2------3------4--
trigger  -------o----o-o--o----o-----
         source.Sample(trigger)
         -------1----2---------3-----
         .CombineLatest(trigger,(s,t)=>s)
         -------1----2-2--2----3-----

CombineLatest().DistictUntilChanged()

triggerをとにかく前回と違う値を流すストリームに変換して(例えばScan()でインクリメントさせる)
sourceとCombineLatest()して、trigger変換ストリームの値をキーにしてDistictUntilChanged()する。

source   ---1-------2--------3------4--------
trigger  -------o------o--o-----o------o-----
trigger' -------A------B--C-----D------E-----
         .CombineLatest(trigger,(s,t)=>new {src = s, trg = t})
         -------1A--2A-2B-2C-3C-3D--4D-4E----
         .DistictUntilChanged(x => x.trg)
         -------1A-----2B-2C----3D-----4E----
         .Select(x => x.src)
         -------1------2--2-----3------4-----

おわり

定番パターン集みたいなのが少ないので、いまいち自分がまわりくどいor不確実な実装をしているような気がしてならない。