Observable.GenerateおよびList列挙

リアクティブエクステンション(別名Rx-リアクティブエクステンション)のライブラリには、ヘルパーメソッドObservableがあり ます。 Generateを使用すると、単純な観測可能なシーケンスを生成できます。



IObservable<string> xs = Observable.Generate<int, string>( initialState: 0, //   condition: x => x < 10, //    iterate: x => x + 1, //   resultSelector: x => x.ToString() //      ); xs.Subscribe(x => Console.WriteLine("x: {0}", x));
      
      









一般化されたメソッドのパラメーターの明示的な指示に注意してください。 この奇妙な振る舞いの理由(結局、メソッドの一般化されたパラメーターはコンパイラーによって静かに出力されるべきです)は、C#言語コンパイラーに既知のバグがあるためです。その結果、型推論は名前付きパラメーターに馴染みません。 詳細については、 stackoverflow または connect-eを ご覧ください はい、ところで、このことはVisual Studio 11で修正されました。



Generateメソッドは、同じステップで構成される単純なforループに似ています。初期値の初期化、ループからの終了条件、ループ変数の変更です。 また、メモリ内に通常のシーケンスを生成する場合は、イテレーターブロック内でforループを使用します。



 static IEnumerable<string> GenerateSequence() { for ( int x = 0; //initialState x < 10; // condition x = x + 1 // iterate ) { yield return x.ToString(); // resultSelector } } static void Main(string[] args) { var xs = GenerateSequence(); foreach (var x in xs) Console.WriteLine("x: {0}", x); }
      
      







コードの両方のバージョンは同等ですが、最初のケースでプッシュシーケンスが( TインターフェイスのIObservable 形式で)作成され、独立して新しい要素の到着を通知する場合、2番目のケースではプルシーケンスを取得します( TインターフェイスのIEnumerable 形式で)、これらの要素を「ペンチ」で「引き出す」必要があります。





要するに、 プルモデルは、アプリケーションが主導的な役割を果たすときの、アプリケーションと外界との相互作用のそのようなモデルです。 IEnumerableインターフェイスを見ると、 MoveNextメソッドを呼び出して次の要素に進むことで要素のフローを制御するのは呼び出しコードです。 プッシュモデルでは、アクションは異なる方法で展開されます。外部環境自体がアプリケーションにいくつかのイベント(たとえば、新しいデータの存在)を通知し、アプリケーションはそれらに「応答する」だけです。 これらのモデル、 IEnumerable / IObservableインターフェースの二元性、 および リアクティブエクステンション ライブラリの他の機能については、記事: リアクティブエクステンションと非同期操作を ご覧ください



プッシュシーケンスとプルシーケンス(それぞれTの IObservable Tの IEnumerable )のインターフェースは非常に似ているので、ある形式のシーケンスから別の形式に変換するという考えは簡単に思い浮かびます。



 int[] ai = new[] {1, 2, 3}; IObservable<int> oi = Observable.Generate( /*initialState:*/ ai.GetEnumerator(), /*condition:*/ e => e.MoveNext(), /*iterate:*/ e => e, /*resultSelector:*/ e => (int)e.Current ); oi.Subscribe(i => Console.WriteLine("i: {0}", i));
      
      







一般に、犯罪者はいません。このコードを実行すると、次のようになります。



1

2

3



しかし、配列の代わりにリストで同じことをしたい場合はどうなりますか:



 List<int> li = new List<int> {1, 2, 3}; IObservable<int> oi = Observable.Generate( /*initialState:*/ li.GetEnumerator(), /*condition:*/ e => e.MoveNext(), /*iterate:*/ e => e, /*resultSelector:*/ e => (int)e.Current ); oi.Subscribe(i => Console.WriteLine("i: {0}", i));
      
      







そして、非常に自然に、この場合、ゼロの無限シーケンスが得られます。





はい、単純なシーケンスを観測可能なシーケンスに変換する最も簡単な方法は、 Observable拡張メソッドを使用することです。 ToObservableは、 IEnumerableインターフェイスを「拡張」します。 しかし、それについて知らないか、 Generateメソッドで利用可能なより複雑なロジックが必要だとします。



この動作の理由は Tクラスのリスト (および他のほとんどのBCLコレクション)の列挙子がクラスではなく構造であるという事実にあります。 そして、私たちが知っているように、変数構造(結局、列挙子はその内部状態を変更します)は、値による送信に実際には適合しません。 この結果、 Generateメソッドで渡した元の列挙子ではなく、常に列挙子のコピーを変更しようとしています。



変数構造は悪であり、列挙子の最も一般的な使用例に合わせてコードを最適化したいという要望のみが、BCL開発者にクラスではなく構造を作成させました。 C#2.0の列挙子の最も一般的な使用例は、可変構造の問題に悩まされないforeachループで使用することであったため、いくつかのプロセッササイクルを節約し、列挙子のクラスではなく構造を使用することにしました。





この方法でこれが行われた理由の詳細は Sasha Goldshtein の投稿の1つと、 stackoverflowに関するEric Lippertの 回答に記載さ れています。 可変の重要なタイプの他の問題は記事で見つけることができます: 可変の重要なタイプの危険性について



この問題は非常に簡単に解決されます。このためには、列挙子の初期値をインターフェイスに持っていくだけで十分です。これにより、パッケージを作成し、管理ヒープにある「共通」変数を変更し、コピーを変更しません:



 IObservable<int> oi = Observable.Generate( /*initialState:*/ (IEnumerator<int>)li.GetEnumerator(), /*condition:*/ e => e.MoveNext(), /*iterate:*/ e => e, /*resultSelector:*/ e => (int)e.Current );
      
      






All Articles