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以䞊のオペレヌタヌがサポヌトされおいたす。



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を䜿甚する䟋が含たれおいたす。

Source: https://habr.com/ru/post/J445098/


All Articles