次の方法で共有


パフォーマンスの最適化: データ バインディング

Windows Presentation Foundation (WPF) データ バインディングは、アプリケーションがデータを表示および操作するためのシンプルで一貫性のある方法を提供します。 要素は、CLR オブジェクトと XML の形式で、さまざまなデータ ソースのデータにバインドできます。

このトピックでは、データ バインディングのパフォーマンスに関する推奨事項について説明します。

データ バインディングの参照が解決されるしくみ

データ バインディングのパフォーマンスの問題について説明する前に、Windows Presentation Foundation (WPF) データ バインディング エンジンがバインディングのオブジェクト参照をどのように解決するかを調べる価値があります。

Windows Presentation Foundation (WPF) データ バインディングのソースには、任意の CLR オブジェクトを指定できます。 CLR オブジェクトのプロパティ、サブプロパティ、またはインデクサーにバインドできます。 バインディング参照は、Microsoft .NET Framework リフレクションまたは ICustomTypeDescriptorを使用して解決されます。 バインディングのオブジェクト参照を解決するための 3 つの方法を次に示します。

最初の方法では、リフレクションを使用します。 この場合、PropertyInfo オブジェクトは、プロパティの属性を検出するために使用され、プロパティ メタデータへのアクセスを提供します。 ICustomTypeDescriptor インターフェイスを使用する場合、データ バインディング エンジンはこのインターフェイスを使用してプロパティ値にアクセスします。 ICustomTypeDescriptor インターフェイスは、オブジェクトに静的なプロパティ セットがない場合に特に便利です。

プロパティ変更通知は、INotifyPropertyChanged インターフェイスを実装するか、TypeDescriptorに関連付けられている変更通知を使用して提供できます。 ただし、プロパティ変更通知を実装するための推奨される戦略は、INotifyPropertyChangedを使用する方法です。

ソース オブジェクトが CLR オブジェクトで、ソース プロパティが CLR プロパティである場合、Windows Presentation Foundation (WPF) データ バインディング エンジンは、最初にソース オブジェクトのリフレクションを使用して TypeDescriptorを取得し、次に PropertyDescriptorを照会する必要があります。 この一連のリフレクション操作は、パフォーマンスの観点から非常に時間がかかる可能性があります。

オブジェクト参照を解決する 2 番目のメソッドには、CLR インターフェイスを実装する INotifyPropertyChanged ソース オブジェクトと、CLR プロパティであるソース プロパティが含まれます。 この場合、データ バインディング エンジンはソースの種類に対してリフレクションを直接使用し、必要なプロパティを取得します。 これはまだ最適な方法ではありませんが、最初の方法よりもワーキング セット要件のコストが低くなります。

オブジェクト参照を解決するための 3 番目のメソッドには、DependencyObject であるソース オブジェクトと、DependencyPropertyであるソース プロパティが含まれます。 この場合、データ バインディング エンジンはリフレクションを使用する必要はありません。 代わりに、プロパティ エンジンとデータ バインディング エンジンが一緒にプロパティ参照を個別に解決します。 これは、データ バインディングに使用されるオブジェクト参照を解決するための最適な方法です。

次の表は、これら 3 つのメソッドを使用して、1,000 Text 要素の TextBlock プロパティをバインドするデータの速度を比較しています。

TextBlock の Text プロパティをバインドする バインディング時間 (ミリ秒) レンダリング時間(バインド作業を含む)(ミリ秒)
CLR オブジェクトのプロパティに対して 115 314
CLR を実装する INotifyPropertyChanged オブジェクトのプロパティに対して 115 305
DependencyPropertyDependencyObject 90 263

大きな CLR オブジェクトへのバインド

何千ものプロパティを持つ単一の CLR オブジェクトにデータをバインドすると、パフォーマンスに大きな影響があります。 この影響を最小限に抑えるには、プロパティが少ない複数の CLR オブジェクトに 1 つのオブジェクトを分割します。 次の表は、1 つの大きな CLR オブジェクトと複数の小さなオブジェクトに対するデータ バインディングのバインドとレンダリング時間を示しています。

1000個のTextBlockオブジェクトをデータバインディング バインディング時間 (ミリ秒) レンダリング時間(バインド作業を含む)(ミリ秒)
プロパティが 1000 の CLR オブジェクトへ 950 1200
1 つのプロパティを持つ 1000 個の CLR オブジェクト 115 314

ItemsSource へのバインディング

CLRに表示する従業員の一覧を保持する List<T>ListBox オブジェクトがあるシナリオを考えてみましょう。 これら 2 つのオブジェクト間の対応を作成するには、従業員リストを ItemsSourceListBox プロパティにバインドします。 ただし、新しい従業員がグループに参加したとします。 この新しいユーザーをバインドされた ListBox 値に挿入するには、このユーザーを従業員リストに追加するだけで、この変更がデータ バインディング エンジンによって自動的に認識されることを期待する場合があります。 その仮定は間違っているだろう。実際には、変更は ListBox に自動的には反映されません。 これは、CLRList<T> オブジェクトがコレクション変更イベントを自動的に発生させないためです。 変更を取得する ListBox を取得するには、従業員のリストを再作成し、ItemsSourceListBox プロパティに再アタッチする必要があります。 このソリューションは機能しますが、パフォーマンスに大きな影響を与えます。 ItemsSourceListBox を新しいオブジェクトに再割り当てするたびに、ListBox は最初に前の項目を削除し、リスト全体を再生成します。 ListBox が複雑な DataTemplateにマップされると、パフォーマンスへの影響が大きくなります。

この問題に対する非常に効率的な解決策は、従業員リストを ObservableCollection<T>にすることです。 ObservableCollection<T> オブジェクトは、データ バインディング エンジンが受け取ることができる変更通知を生成します。 イベントは、リスト全体を再生成する必要なく、ItemsControl から項目を追加または削除します。

次の表は、1 つの項目が追加されたときに (UI 仮想化がオフになっている) ListBox の更新にかかる時間を示しています。 最初の行の数値は、CLRList<T> オブジェクトが ListBox 要素の ItemsSourceにバインドされたときの経過時間を表します。 2 行目の数値は、ObservableCollection<T>ListBox 要素の ItemsSourceにバインドされたときの経過時間を表します。 ObservableCollection<T> データ バインディング戦略を使用すると、大幅な時間の節約に注意してください。

ItemsSource のデータ バインディングのバインド先 1 項目 (ミリ秒) の更新時刻
CLR List<T> オブジェクトへ 1656
ObservableCollection<T> 20

IList を IEnumerable ではなく ItemsControl にバインドする

IList<T> または IEnumerableItemsControl オブジェクトにバインドする場合は、IList<T> オブジェクトを選択します。 IEnumerableItemsControl にバインドすると、WPF はラッパー IList<T> オブジェクトを強制的に作成します。つまり、パフォーマンスは 2 番目のオブジェクトの不要なオーバーヘッドの影響を受けます。

CLR オブジェクトを XML Just for Data Binding に変換しないでください。

WPF を使用すると、XML コンテンツにデータをバインドできます。ただし、XML コンテンツへのデータ バインディングは、CLR オブジェクトへのデータ バインディングよりも低速です。 唯一の目的がデータ バインディングの場合 CLR オブジェクト データを XML に変換しないでください。

こちらも参照ください