DynamicData:コレクション、MVVMデザインパターン、およびリアクティブエクステンションの変更

2019年2月、Microsoft .NETプラットフォームでGUIアプリケーションを構築するためのクロスプラットフォームフレームワークであるReactiveUI 9がリリースされました。 ReactiveUIは、 リアクティブエクステンションをMVVMデザインパターンと密接に統合するためのツールです。 フレームワークの知識 は、 Habréの一連の記事またはドキュメントのフロントページから始めることができます 。 ReactiveUI 9アップデートには多くの修正と改善が含まれていますが、おそらく最も興味深い重要な変更点はDynamicDataフレームワークとの緊密な統合であり 、これによりリアクティブスタイルでコレクションの変更を操作できます 。 どのような場合にDynamicData役立つか、この強力なリアクティブフレームワークがどのように内部に配置されているかを把握してみましょう。



背景



最初に、 DynamicDataが解決するタスクの範囲を定義し、 System.Collections.ObjectModel



名前空間からのデータセットの変更を処理するための標準ツールが私たちに合わない理由を見つけます



ご存じのとおり、MVVMテンプレートには、アプリケーションのモデル、プレゼンテーション、およびプレゼンテーションモデルのレイヤー間の責任の分割が含まれます。 モデル層はドメインエンティティとサービスによって表され、プレゼンテーションモデルについては何も知りません。 モデルレイヤーは複雑なアプリケーションロジック全体をカプセル化し、プレゼンテーションモデルはモデルの操作を委任し、観察可能なプロパティ、コマンド、コレクションを通じてアプリケーションの現在の状態に関する情報へのビューアクセスを許可します。 プロパティの変更を処理する標準ツールは、 INotifyPropertyChanged



インターフェイス、ユーザーアクションを処理するINotifyPropertyChanged



、およびINotifyCollectionChanged



実装し、 ObservableCollection



ReadOnlyObservableCollection



を実装するINotifyCollectionChanged











INotifyPropertyChanged



ICommand



の実装は、通常、開発者と使用されるMVVMフレームワークの良心に残りますが、 ObservableCollection



の使用には多くの制限が課せられます! たとえば、 Dispatcher.Invoke



または同様の呼び出しを行わずにバックグラウンドスレッドからコレクションを変更することはできません。これは、バックグラウンド操作がサーバーと同期するデータ配列を操作する場合に役立ちます。 慣用的なMVVMでは、モデル層は使用されるGUIアプリケーションアーキテクチャを知る必要がなく、MVCまたはMVPからのモデルと互換性がある必要があり、これが多くのDispatcher.Invoke



が実行中のバックグラウンドスレッドからユーザーインターフェイスコントロールへのアクセスを許可する理由であるドメインサービスでは、アプリケーションレイヤー間で責任を共有するという原則に違反します。



もちろん、ドメインサービスでは、イベントを宣言し、変更されたデータをイベントの引数としてチャンクを転送できます。 次に、イベントにサブスクライブし、使用するGUIフレームワークに依存しないようにDispatcher.Invoke



呼び出しをインターフェイスでラップし、必要に応じてDispatcher.Invoke



をプレゼンテーションモデルに移動し、 ObservableCollection



変更ObservableCollection



ます。 。 勉強を始めましょう!



リアクティブ拡張。 データストリームを管理する



DynamicDataによって導入された抽象化と、リアクティブなデータセットの変更に関する原則を完全に理解するために、 リアクティブなプログラミングとは何か、Microsoft .NETプラットフォームとMVVMデザインパターンのコンテキストでそれを適用する方法を思い出しみましょう。 プログラムコンポーネント間の対話を整理する方法は、対話型で反応型にすることができます。 インタラクティブな相互作用では、コンシューマー関数はプロバイダー関数からデータを同期的に受け取り( IEnumerable



アプローチ、 T



IEnumerable



)、リアクティブな相互作用では、コンシューマー関数は非同期的にデータをコンシューマー関数に配信します(プッシュベースのアプローチ、 Task



IObservable



)。







リアクティブプログラミングは、非同期データストリームを使用したプログラミングであり、リアクティブエクステンションは、System名前空間のIObservable



およびIObserver



に基づく実装の特別なケースです。これは、LINQ over Observableと呼ばれるIObservable



インターフェイスでのLINQに似た操作の数を定義します。 リアクティブ拡張機能は.NET Standardをサポートし、Microsoft .NETプラットフォームが機能する場所であればどこでも機能します。







ReactiveUIフレームワークは、アプリケーション開発者がICommand



およびINotifyPropertyChanged



リアクティブ実装を利用するように招待し、 ReactiveCommand<TIn, TOut>



およびWhenAnyValue



などの強力なツールを提供します。 WhenAnyValue



使用WhenAnyValue



と、INotifyPropertyChangedを実装するクラスのプロパティをIObservable<T>



型のイベントストリームに変換できWhenAnyValue



。これにより、依存プロパティの実装が簡単になります。



 public class ExampleViewModel : ReactiveObject { [Reactive] //  ReactiveUI.Fody,  // -  // OnPropertyChanged   Name. public string Name { get; set; } public ExampleViewModel() { //  OnPropertyChanged("Name"). this.WhenAnyValue(x => x.Name) //   IObservable<string> .Subscribe(Console.WriteLine); } }
      
      





ReactiveCommand<TIn, TOut>



では、タイプIObservable<TOut>



イベントのようにコマンドをReactiveCommand<TIn, TOut>



できます。これは、コマンドの実行が完了するたびに発行されます。 また、すべてのコマンドには、タイプIObservable<Exception>



ThrownExceptions



プロパティがありIObservable<Exception>







 // ReactiveCommand<Unit, int> var command = ReactiveCommand.Create(() => 42); command //   IObservable<int> .Subscribe(Console.WriteLine); command .ThrownExceptions //   IObservable<Exception> .Select(exception => exception.Message) //   IObservable<string> .Subscribe(Console.WriteLine); command.Execute().Subscribe(); // : 42
      
      





今回は、監視対象のオブジェクトの状態が変化するたびにタイプT



新しい値を発行するイベントのように、 IObservable<T>



を使用しました。 簡単に言えば、 IObservable<T>



はイベントのストリームであり、時間の経過とともに引き伸ばされたシーケンスです。



もちろん、コレクションを簡単かつ自然に操作できます。コレクションが変更されるたびに、変更された要素を含む新しいコレクションを公開します。 この場合、公開された値はIEnumerable<T>



型またはより特殊化されたものになり、イベント自体はIObservable<IEnumerable<T>>



型にIObservable<IEnumerable<T>>



。 しかし、批判的に考えている読者が正しく指摘しているように、これはアプリケーションのパフォーマンスに深刻な問題を抱えています。特にコレクションに12個の要素がなく、100個、または数千個の要素がある場合です。



DynamicDataの概要



DynamicDataは、コレクションを操作するときにリアクティブエクステンションのすべての機能を使用できるようにするライブラリです。 すぐに使えるリアクティブ拡張は、データセットの変更に対応する最適な方法を提供しません。DynamicDataの仕事はそれを修正することです。 ほとんどのアプリケーションでは、コレクションを動的に更新する必要があります。通常、コレクションはアプリケーションの起動時にいくつかの要素で満たされ、その後非同期に更新されて、サーバーまたはデータベースと情報を同期します。 最新のアプリケーションは非常に複雑であり、コレクションの投影を作成する必要があります-要素のフィルター、変換、または並べ替え。 DynamicDataは、動的に変化するデータセットを管理するために必要な非常に複雑なコードを取り除くために設計されました。 このツールは積極的に開発および完成され、現在、コレクションを操作するための60以上のオペレーターがサポートされています。







DynamicDataObservableCollection<T>



代替実装ではありません。 DynamicDataアーキテクチャは、主にドメイン固有のプログラミングの概念に基づいています。 使用のイデオロギーは、データソース、つまりデータの同期と変更を担当するコードがアクセスできるコレクションを管理するという事実に基づいています。 次に、ソースにいくつかの演算子を適用します。これにより、他のコレクションを手動で作成および変更することなく、宣言的にデータを変換できます。 実際、 DynamicDataでは読み取り操作と書き込み操作を分離し、リアクティブな方法でのみ読み取りが可能です。したがって、継承されたコレクションは常にソースと同期されます。



DynamicDataは、従来のIObservable<T>



代わりに、 IObservable<IChangeSet<T>>>



およびIObservable<IChangeSet<TValue, TKey>>



操作を定義しますIChangeSet



は、コレクションの変更(変更の種類と影響を受けた要素)に関する情報を含むチャンクです。 このアプローチにより、リアクティブスタイルで記述されたコレクションを操作するためのコードのパフォーマンスが大幅に向上します。 同時に、コレクションのすべての要素を一度に処理する必要が生じた場合、 IObservable<IChangeSet<T>>



は常に通常のIObservable<IEnumerable<T>>



変換できます。 それが複雑に聞こえる場合-心配しないで、コード例からすべてが明確で透明になります!



DynamicDataの例



一連の例を見て、DynamicDataの仕組み、 System.Reactive



との違い、GUIを使用したアプリケーションソフトウェアの通常の開発者が解決できるタスクを理解するために、よく見てみましょう。 GitHubにDynamicDataが投稿した包括的な例から始めましょう。 この例では、データソースはSourceCache<Trade, long>



、トランザクションのコレクションが含まれています。 タスクは、アクティブなトランザクションのみを表示し、モデルをプロキシオブジェクトに変換し、コレクションをソートすることです。



 //    System.Collections.ObjectModel, //       . ReadOnlyObservableCollection<TradeProxy> list; //   ,   . //   Add, Remove, Insert   . var source = new SourceCache<Trade, long>(trade => trade.Id); var cancellation = source //      . //   IObservable<IChangeSet<Trade, long>> .Connect() //       . .Filter(trade => trade.Status == TradeStatus.Live) //    -. //   IObservable<IChangeSet<TrandeProxy, long>> .Transform(trade => new TradeProxy(trade)) //    . .Sort(SortExpressionComparer<TradeProxy> .Descending(trade => trade.Timestamp)) //  GUI    . .ObserveOnDispatcher() //    - //    System.Collections.ObjectModel. .Bind(out list) // ,       //    . .DisposeMany() .Subscribe();
      
      





上記の例では、データソースであるSourceCache



を変更するSourceCache



、それに応じてReadOnlyObservableCollection



も変更されます。 この場合、コレクションから要素を削除すると、 Dispose



メソッドが呼び出され、コレクションは常にGUIストリームでのみ更新され、ソートおよびフィルターされたままになります。 クールで、 Dispatcher.Invoke



と複雑なコードはありません!



SourceListおよびSourceCacheデータソース



DynamicDataは、可変データソースとして使用できる2つの特別なコレクションを提供します 。 これらのコレクションのタイプはSourceList



およびSourceCache<TObject, TKey>



です。 TObject



に一意のキーがある場合は常にSourceCache



を使用することをお勧めします。それ以外の場合はSourceList



使用しSourceList



。 これらのオブジェクトは、 Add



Remove



Insert



、データを変更するための使い慣れた.NET開発者APIを提供します。 データソースをIObservable<IChangeSet<T>>



またはIObservable<IChangeSet<T, TKey>>



.Connect()



するには、 .Connect()



演算子を使用します。 たとえば、バックグラウンドで要素のコレクションを更新するサービスがある場合、 Dispatcher.Invoke



やアーキテクチャの過剰を使用せずに、これらの要素のリストをGUIと簡単に同期できます。



 public class BackgroundService : IBackgroundService { //    . private readonly SourceList<Trade> _trades; //     . //  ,     , //    Publish()  Rx. public IObservable<IChangeSet<Trade>> Connect() => _trades.Connect(); public BackgroundService() { _trades = new SourceList<Trade>(); _trades.Add(new Trade()); //    ! //    . } }
      
      





DynamicDataは、組み込みの.NETタイプを使用して、データを外部にマップします。 強力なDynamicData演算子を使用して、 IObservable<IChangeSet<Trade>>



をビューモデルのReadOnlyObservableCollection



変換できます。



 public class TradesViewModel : ReactiveObject { private readonly ReadOnlyObservableCollection<TradeVm> _trades; public ReadOnlyObservableCollection<TradeVm> Trades => _trades; public TradesViewModel(IBackgroundService background) { //   ,  ,  //     System.Collections.ObjectModel. background.Connect() .Transform(x => new TradeVm(x)) .ObserveOn(RxApp.MainThreadScheduler) .Bind(out _trades) .DisposeMany() .Subscribe(); } }
      
      





Transform



Filter



Sort



に加えて、DynamicDataには他の多くの演算子が含まれており、グループ化、論理演算、コレクションの平滑化、集約関数の使用、同一要素の除外、要素のカウント、プレゼンテーションモデルのレベルでの仮想化までをサポートしています。 GitHubのREADMEプロジェクトにあるすべてのオペレーターの詳細を読んでください。







シングルスレッドコレクションと変更追跡



SourceList



SourceCache



加えて、DynamicDataライブラリには、変更可能なコレクションの単一スレッド実装であるObservableCollectionExtended



も含まれています。 ビューモデルの2つのコレクションを同期するには、1つをObservableCollectionExtended



として宣言し、もう1つをReadOnlyObservableCollection



として宣言し、 ToObservableChangeSet



演算子を使用しConnect



これはConnect



と同じように動作しConnect



ObservableCollection



と連携するように設計されています



 //   . ReadOnlyObservableCollection<TradeVm> _derived; //    -. var source = new ObservableCollectionExtended<Trade>(); source.ToObservableChangeSet(trade => trade.Key) .Transform(trade => new TradeProxy(trade)) .Filter(proxy => proxy.IsChecked) .Bind(out _derived) .Subscribe();
      
      





DynamicDataは、 INotifyPropertyChanged



インターフェイスを実装するクラスの変更の追跡もサポートしています。 たとえば、アイテムのプロパティが変更されるたびにコレクションの変更を通知する場合は、 AutoRefresh



を使用して、目的のプロパティのセレクターを引数で渡します。 AutoRefesh



およびその他のDynamicData演算子を使用すると、画面に表示される膨大な数のフォームおよびサブフォームを簡単かつ自然に検証できます。



 //  IObservable<bool> var isValid = databases .ToObservableChangeSet() //      IsValid. .AutoRefresh(database => database.IsValid) //       . .ToCollection() // ,    . .Select(x => x.All(y => y.IsValid)); //   ReactiveUI, IObservable<bool> //     ObservableAsProperty. _isValid = isValid .ObserveOn(RxApp.MainThreadScheduler) .ToProperty(this, x => x.IsValid);
      
      





DynamicData機能に基づいて、かなり複雑なインターフェイスをすばやく作成できます。これは、大量のリアルタイムデータを表示するシステム、インスタントメッセージングシステム、および監視システムに特に当てはまります。







おわりに



リアクティブエクステンションは、データとユーザーインターフェイスを宣言的に操作し、移植可能なサポートされているコードを記述し、複雑な問題をシンプルかつエレガントな方法で解決できる強力なツールです。 ReactiveUIを使用すると、.NET開発者はINotifyPropertyChanged



およびICommand



リアクティブな実装を提供することにより、MVVMアーキテクチャを使用してプロジェクトにリアクティブな拡張機能を緊密に統合できます。



ReactiveUIおよびDynamicDataライブラリは 、Windows Presentation Foundation、Universal Windows Platform、 Avalonia 、Xamarin.Android、Xamarin Forms、Xamarin.iOSなど、最も一般的な.NET Framework GUIフレームワークと互換性があります対応するReactiveUIドキュメントページからDynamicDataの学習を開始できます 。 また、 DynamicData Snippetsプロジェクトを必ずチェックしてください。このプロジェクトには、あらゆる場面でDynamicDataを使用する例が含まれています。



All Articles