.doと.subscribeの違い(SwiftとC#から見た)


.doと.subscribeの違い

気になった記事の該当部分のコピペ抜粋です あとできっちりまとめる予定

Rx時代の先にあるもの

Rxの場合

do-swift.swift
incrementCount
    .do { increment in self.counter2 += increment } // 1
    .map { increment in increment * 2 } // 2
    .do { self.updateDatabase() } // 3
    .subscribe { newIncrement in // 4
        self.counter += newIncrement
    }

do 自分の外の状態(Counter2)を変更する分かりにくいクロージャ
map 引数の値を2倍にして返すだけ。それ以外に余計なこと(クロージャ外の状態変えたり)しない分かりやすいクロージャ
do データベースを操作しローカルの環境を大きく変える分かりにくいクロージャ
subscribe 自分の外の状態(Counter)を変更する分かりにくクロージャ
Rxでは登録したクロージャは分かりやすいもの、そうでないもの、明確な区別なくObserverに保持されます。また今回はmapを分かりやすいクロージャにしましたが、
map内でスコープの広い状態の変更やデータベース操作などをすることで、分かりにくいクロージャにすることもできます。この辺は制約が無いため、Rxを使う人によってかなり幅がでます。

Reactive Extensions再入門 その10「Doメソッド」

Doメソッド
DoメソッドはIObservableから発行された値を受けて処理を行うだけのメソッドです。5.4.1のDrag処理でも使用しましたが、要素を受け取って処理を行いIObservableの要素に対しては何も加工を行ったりしません。コード例を下記に示します。

do-c#.cs
var subject = new Subject<int>();
subject
    // 途中に処理を挟む
    .Do(i => Console.WriteLine("Do : {0}", i))
    // 購読(購読しないとOnNextをしても値が流れないね)
    .Subscribe(i => Console.WriteLine("OnNext : {0}", i));

// 値の発行
subject.OnNext(1);
subject.OnNext(2);
subject.OnNext(3);

このコードを実行すると、Subscribeで値を購読している処理の前にDoの処理が行われていることが確認出来ます。

Do : 1
OnNext : 1
Do : 2
OnNext : 2
Do : 3
OnNext : 3

連載:Reactive Extensions(Rx)入門 | 第2回 イベント・プログラミングとRx

IObserverオブジェクトの詳細
最初の「同じ実行結果のRxとforeach文のコード」の例ではIObserverインターフェイスのメンバのうち、OnNextメソッドのデリゲートしか記述していなかった。このOnNextは、値がオブザーバに届くたびに実行されるメソッドで、最も利用されるものだろう。残りの2つのうち、OnErrorは例外発生時に実行されるメソッド、OnCompletedはシーケンスが完了後に実行されるメソッドを表す。これら3つのメソッドのデリゲートを指定する場合の、Subcribeメソッドのコード例を示す。

do-c#.cs
// 正常終了する場合 
// 実行結果:1, 2, 3, 4, 5, Completed 
Observable.Range(1, 5) 
   .Subscribe(
     x => Console.WriteLine(x), 
     ex => Console.WriteLine("Error"), 
     () => Console.WriteLine("Completed")); 

// 途中で例外が発生する場合 
// 実行結果:1, 2, Error 
Observable.Range(1, 5) 
   .Do(x => { if (x == 3) throw new Exception(); }) 
   .Subscribe( 
     x => Console.WriteLine(x), 
     ex => Console.WriteLine("Error"), 
     () => Console.WriteLine("Completed")); 

do-vb.vb


' 正常終了する場合 
' 実行結果:1, 2, 3, 4, 5, Completed 
 Observable.Range(1, 5). 
   Subscribe( 
     Sub(x) Console.WriteLine(x), 
     Sub(ex) Console.WriteLine("Error"), 
     Sub() Console.WriteLine("Completed")) 

 ' 途中で例外が発生する場合 
 ' 実行結果:1, 2, Error 
 Observable.Range(1, 5). 
   Do(Sub(x) 
       If (x = 3) Then 
         Throw New Exception() 
       End If 
     End Sub). 
   Subscribe( 
     Sub(x) Console.WriteLine(x), 
     Sub(ex) Console.WriteLine("Error"), 
     Sub() Console.WriteLine("Completed")) 

OnError/OnCompletedメソッドのデリゲートを含めたSubcribeメソッドのコードの実行例(上:C#、下:VB)
Doメソッドはパイプラインを通過する値に処理を加えて、元の値はそのまま後続へ渡す。今回は値が「3」のときに例外を発生させるようにした。
上記のコードを実行した場合、Rangeメソッドにより「1」~「5」の値の生成が完了すると、OnCompletedメソッドが呼ばれる、もしくは途中で例外が発生するとOnErrorメソッドが呼ばれるのが確認できるだろう。なお、OnErrorメソッド、もしくはOnCompletedメソッドは、どちらか片方しか呼ばれることはない。つまり、OnErrorメソッドが呼ばれた後にOnCompletedメソッドが呼ばれたりすることはない。また、OnErrorメソッド、もしくはOnCompletedメソッドが呼ばれた後にOnNextメソッドが呼ばれることもない*3。

*3 この法則はRxに標準で用意されているメソッドではすべて守られている。また、メソッドを自作する場合でもObservable.CreateメソッドといったRx標準で用意されている生成メソッドを用いれば守られる。さらにSubscirbeメソッドの呼び出し時にもRxで用意されているObserver(前述のSystem.ObservableExtensionsクラスによる拡張メソッド、もしくはObserver.Createメソッド)を使えば自動的に守られるようになっている。このように、幾重にも渡って法則が厳守されるようになっているため、ほぼ100%この法則が破られることはないが、IObservable<T>/IObserver<T>オブジェクトをすべて一から自前で実装する場合は、この法則に反する挙動をする可能性もある。ただし、そのような実装はすべきではない。安全のためにも、IObservable<T>インターフェイスやIObserver<T>インターフェイスを実装する際は、極力、Rxに用意されているObservable.Create/Observer.Createメソッドを利用して実装すべきだろう。どうしてもそれらが利用できない場合は、法則を守るように注意深く実装する必要がある。