過去記事インデックス
- Reactive Extensions再入門 その1
- Reactive Extensions再入門 その2「IObservableインターフェースとIObserverインターフェース」
- Reactive Extensions再入門 その3「IObservableのファクトリメソッド」
- Reactive Extensions再入門 その4「Timer系のファクトリメソッド」
- Reactive Extensions再入門 その5「HotとCold」
- Reactive Extensions再入門 その6「HotなIObservableを作成するファクトリ」
- Reactive Extensions再入門 その7「LINQスタイルの拡張メソッド」
- Reactive Extensions再入門 その8「SkipとTakeメソッド」
はじめに
基本的なことばかりでは飽きてきたので、ここらへんで息抜きとして、ここまででやっていないメソッドとかも駆使してReactive Extensionsを使うことで、どれだけプログラムがシンプルに書けるのか遊んでみようと思います。お題は以下の通りです。
センサー監視
下記の要件を満たすプログラムを作成します。
10個のセンサーがあります。10個のセンサーは、それぞれに識別用の名前が与えられていて1秒間隔で数値(1〜1000)を上げてきます。プログラムでは10秒間の間に一番大きな値を上げてきたセンサーの名前と値をコンソールに出力します。
ということで作成していきます。実際にセンサーは無いので下記のような疑似的なセンサーを表すクラスをセンサーに見立ててプログラムを書きます。
namespace SensorSample { using System; using System.Threading; // センサーを表すクラス class Sensor { // センサーの値はとりあえず乱数で発行する private static Random random = new Random(); // センサーの値を発行する間隔を制御するタイマー private Timer timer; // センサー名 public string Name { get; private set; } // 名前をつけてセンサー作成 public Sensor(string name) { this.Name = name; } // センサーの値の発行開始 public void Start() { this.timer = new Timer(_ => { this.OnPublish(random.Next(1001)); }, null, 0, 1000); } // センサーの値発行イベント public EventHandler<SensorEventArgs> Publish; // センサーから値を発行する private void OnPublish(int value) { var h = this.Publish; if (h != null) { h(this, new SensorEventArgs(this.Name, value)); } } } // センサーが値を発行したときのイベント引数 class SensorEventArgs : EventArgs { // 値を発行したセンサー名 public string Name { get; private set; } // 発行した値 public int Value { get; private set; } public SensorEventArgs(string name, int value) { this.Name = name; this.Value = value; } } }
このプログラムをベースにReactive Extensionsを使ってプログラムを書いていきます。Mainメソッドの中身を下記に示します。
// 10個のセンサーを作成 var sensors = Enumerable.Range(1, 10).Select(i => new Sensor("Sensor#" + i)).ToArray(); // 10個のセンサーの値発行イベントをマージ var subscription = Observable.Merge( sensors.Select(sensor => Observable.FromEvent<EventHandler<SensorEventArgs>, SensorEventArgs>( h => (s, e) => h(e), h => sensor.Publish += h, h => sensor.Publish -= h))) // 10秒ためて .Buffer(TimeSpan.FromSeconds(10)) // その中から最大のものを探して .Select(values => values.Aggregate((x, y) => x.Value > y.Value ? x : y)) // 表示する .Subscribe(e => Console.WriteLine("{0}: {1}", e.Name, e.Value)); // センサースタート foreach (var sensor in sensors) { sensor.Start(); } Console.WriteLine("Sensor started."); Console.ReadLine(); // 最後にセンサーのPublishイベントの購読解除 subscription.Dispose();
時間やイベントを扱うReactive Extensionsの得意分野なので非常にすっきりしたコードになっているのがわかります。恐らく普通にプログラムを書くとかなりの分量になると思われます。10秒間分の値を保持して最大値を探してetc…考えただけでも恐ろしくなります。
このプログラムの実行結果を下記に示します。
Sensor started. Sensor#6: 994 Sensor#7: 997 Sensor#3: 1000 Sensor#1: 982 Sensor#4: 986 Sensor#4: 964 続行するには何かキーを押してください . . .
実行結果の字面上ではわかりませんが、10秒間隔で1行ずつ結果が表示されます。
まとめ
このようにReactive Extensionsを使えば時間、イベント、IObservableの合成、変換などなどを駆使して楽しく簡単?にコードを書くことが出来ます。私はReactive Extensionsのこういう所が大好きです。