背景
最初に、 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以上のオペレーターがサポートされています。
DynamicDataは
ObservableCollection<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を使用する例が含まれています。