Reactive Extensionsでクリーンなコードを書く

時間がかかり、時間の経過とともにいくつかの中間結果を返すことができるプロセスがある場合、 .NET Framework Reactive Extensionsを使用すると、コードを簡素化し、より適切に管理できます。



リアクティブ拡張機能を備えたクリーンなコード



ほとんどの場合、単にメソッドを呼び出して、出力で結果を取得します。 しかし、いくつかのプロセスは異なって配置されています。 たとえば、メソッドは長時間実行される場合があります。 または、さらに悪いことに、このメソッドは実行に時間がかかるだけでなく、実行時に不規則に中間結果を返します。 もちろん、これも含めて、.NET Frameworkにはイベントがあり、1つのオブジェクトが2番目のオブジェクトのメソッドを呼び出して、必要なときにいくつかの情報を渡すことができます。



ただし、イベントを使用するよりも良い方法でこの問題を解決する方法があります-リアクティブ拡張機能です。 長時間実行され、時々中間結果が返されるプロセスがある場合、Reactive Extensionsはそのような結果が来るたびに処理できるようにします。 イベントの代わりにReactive Extensionsを使用するコードは簡単になるだけでなく、機能が豊富になります(たとえば、不要なデータを除外するためにLINQを使用できます)。



リアクティブ拡張は、データストリームを処理する方法としてドキュメントに記載されています。 拡張機能を使用する場合、コレクション内のデータを反復処理し、興味深いものを定期的に検索し、見つけたものをアプリケーションに送信するプロセスを想像するのは難しくありません。これにより、アプリケーションは強制的に反応します(リアクティブ) )。 この例では、アプリケーションが販売注文の一連の変更を実行している間に何かをしたいと仮定します。 これを行うには、 StatusChangedメソッドを記述し、アプリケーションで順序が変更されるたびに呼び出す必要があります。 または、 StatusOrderイベントをSalesOrderクラスに追加し、状態が変わるたびに呼び出すことができます。コードをこのイベントに接続するだけです。



私の意見では、Reactive Extensionsを使用してこの問題を解決することは、上記で提案したものよりも簡単であるだけでなく、LINQとの統合のおかげで、より柔軟です。 確かに、情報源の接続または切断に関しては、Reactive Extensionsを使用したソリューションは、イベントを使用した同様のコードよりも簡単です。



Reactive Extensionsのインストール


最初に、NuGetを使用してReactive Extensionsをプロジェクトに接続する必要があります。 JavaScript、Androidの実装、WebサービスのLINQリクエストの処理など、Reactive Extensionsパッケージがいくつかあります。 必要なパッケージを見つけるには、「Reactive Extensions」というフレーズで検索を開始し、メインライブラリパッケージをプロジェクトに追加します。



2番目のステップは、順序が変更されるたびに返すデータを決定することです。 注文の現在の状態と一緒に、注文識別子も返すことは理にかなっています。 必要なプロパティを使用してこのためのクラスを作成します。

public class StatusChange { public int OrderId { get; set; } public string OrderStatus { get; set; } }
      
      





次のステップでは、このタイプで動作するReactive Extensionsサブジェクトを定義し、アプリケーションの起動時に初期化し、 Subscribeメソッドを使用してサブジェクトのメソッドを呼び出してサブスクライブし、変更に遅れないようにします。



 ISubject<StatusChange> statChange = new Subject<StatusChange>(); statChange.Subscribe(sc => MessageBox.Show(sc.OrderStatus));
      
      





Subscribeメソッドでは、順序が変更されたときに何をしたいかを示すラムダ式を渡します。 この例では、クラスのOrderStatusプロパティの値を表示するだけです。



アプリケーションのどこでも、注文ステータスを変更するときに、 StatusChangeクラスのインスタンスを作成し、そのプロパティを入力して、作成したSubjectのOnNextメソッドを呼び出します。 典型的な初期注文通知コードは次のようになります。



 statChange.OnNext(new StatusChange() { OrderId = 1, OrderStatus = "New" });
      
      





OnNextメソッドが呼び出されるたびに、ラムダ式で定義したStatusChange.OrderStatusプロパティの値を含むメッセージが表示されます。



ソリューション拡張


もちろん、実際のプロジェクトでは、ステータスの変更を処理するには、ラムダ式に収めるよりも多くのコード行が必要になる場合があります。 ラムダ式の代わりに、たとえば次のように、 Subscribeメソッド内の別のメソッドに常にポインターを渡すことができます。



 statChange.Subscribe(StatusChanged);
      
      





メソッドはパラメータを受け入れない場合がありますが、Subjectが関連付けられているのと同じタイプのオブジェクトを引数として取る場合、 OnNextが呼び出されたときに指定されたオブジェクトが渡されます 。 たとえば、このようなメソッドは、新しい状態をコンソールに出力するだけです。



 public static void StatusChanged(StatusChange status) { Console.WriteLine(status.OrderStatus); }
      
      





ステータスを変更するときにいくつかの異なるメソッドを実行する必要がある場合、それらを1つのメソッドにまとめる必要はありません。 代わりに、Subjectを数回呼び出して、必要なメソッドを順番に渡します。



 statChange.Subscribe(StatusChanged); statChange.Subscribe(StatusAudit);
      
      





このアプローチでは、注文(アプリケーション)に変更を加えるプロセスと、これらの変更に応答するプロセス(メソッドStatusChangedStatusAudit )の弱い結合があります。 これらのプロセスをつなぐものは、 StatusChangeクラスの定義だけです。これは、他のプロセスを中断することなく、追加のプロパティで安全に拡張できます。 これまで、これはイベントの使用とそれほど違いはありませんでしたが、コードを作成する必要が少なかった点が異なります。



しかし、Reactive Extensionsを使用すると、コードが単純化されるだけでなく、イベントを上回ることができます。 まず、注文ステータスのすべての変更を処理したくないとしましょう。 たとえば、ステータスが「進行中」に変更された注文のみをキャッチします。 LINQを使用して、サブジェクトから受信する結果を明確にすることができます。 これを試す前に、コードに名前空間System.Reactive.LINQを追加する必要があります。



この名前空間を接続すると、LINQ式を記述したり、LINQ拡張メソッドを使用して、受信して処理する結果を選択できることがわかります。 以下の3つの例はすべて、処理ステータスの変更に対してのみメソッドが呼び出されることを示しています。



 statChange.Where(c => c.OrderStatus == "Processing").Subscribe(ReportStatusChange); var scs = from sc in statChange where sc.OrderStatus == "Processing" select sc; scs.Subscribe(ReportStatusChange); var sub = (from sc in statChange where sc.OrderStatus == "Processing" select sc).Subscribe(ReportStatusChange);
      
      





また、ステータスをエラーに変更するとき、または注文をするときに特別なハンドラーが必要になる場合があります。 これは注文の状態によって適切に判断できますが、Reactive Extensionsはより良い解決策を提供します: OnErrorおよびOnCompleted Subjectメソッドです。 SubjectのSubcribeメソッドを呼び出すと、SubjectメソッドOnErrorおよびOnCompletedが呼び出されたときに実行する必要があるメソッド(またはラムダ式)へのポインターをパラメーターに渡すことができます。 以下の例では、コードをより視覚的にするためにメソッド名が変更されています。



 statChange.Subscribe(OnNext, OnError, OnCompleted);
      
      





OnErrorメソッドはパラメーターとして例外を受け取る必要があり、 OnCompletedメソッドはパラメーターなしである必要があります。 典型的な例は次のとおりです。



 public static void OnError(Exception ex) { Console.Error.WriteLine(ex.Message); } public static void OnCompleted() { Console.WriteLine("order processing completed"); }
      
      





これで、何か問題が発生した場合、アプリケーションはOnError Subject メソッドを呼び出す必要があります。 このメソッドを呼び出すときは、問題に関する情報を含むパラメーターに例外を渡す必要があります(実際のプロジェクトでは、以下の例よりも優れたものがあります)。



 statChange.OnError(new Exception("Something has gone horribly wrong!"));
      
      





アプリケーションが注文の処理を終了したら、 OnCompleted Subject メソッドを呼び出す必要があります。 この状態変更のハンドラーの呼び出しに加えて、このメソッドはサブジェクトに通知を送信しないことも指示します(これは、イベント側ではサブスクライバーを無効にするために、イベントでは実行できないことです)。 また、Subjectは、 Disposeメソッドを呼び出すことにより、すべてのリスナーから自分自身を解放できます。



通知のカプセル化


アプリケーションには1つの問題があります。注文のステータスを変更するすべての場所で、SubjectのOnNextメソッドを呼び出すことを忘れてはなりません。 これを自動化するとよいでしょう。 理想的には、注文クラスのStatusプロパティのセッター内でこの呼び出しを非表示にできます。 これにより、コードの重複や、 OnNextメソッドの呼び出しを忘れる可能性がなくなります。



以下のコードリストでは、 SalesOrderクラスにISubject型のプロパティが含まれています。これは、Subjectのインスタンスを使用してコンストラクターで初期化されます。 これで、SubjectのOnNextメソッドがStatusプロパティが変更されるたびに呼び出されます( OnErrorおよびOnCompleted Subjectメソッドの呼び出しをサポートするために、同じクラスに追加コードを追加できます)。



 public class SalesOrder { string _status; public ISubject<StatusChange> StatChange { get; private set; } public int Id { get; set; } public string Status { get { return _status; } set { _status = value; var sc = new StatusChange() { OrderId = this.Id, OrderStatus = this.Status }; StatChange.OnNext(sc); } } public SalesOrder() { StatChange = new Subject<StatusChange>(); } }
      
      






All Articles