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

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

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

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

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

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

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

INotifyPropertyChanged インターフェイスを実装するか、または TypeDescriptor に関連付けられている変更通知を使用することによって、プロパティ変更通知を提供できます。 ただし、プロパティ変更通知を実装するには INotifyPropertyChanged を使用することをお勧めします。

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

オブジェクト参照を解決するための 2 つ目の方法は、CLR ソース オブジェクトで INotifyPropertyChanged インターフェイスが実装されていて、ソース プロパティが CLR プロパティである場合に使用されます。 この場合、データ バインディング エンジンは、ソースの型に対してリフレクションを直接使用して必要なプロパティを取得します。 この方法も最適な方法とは言えませんが、最初の方法よりは作業セットの要件の負荷が小さくなります。

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

これらの 3 つの方法を使用して 1000 個の TextBlock 要素の Text プロパティのデータ バインディングを行ったときの速度の比較を次の表に示します。

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

大きな CLR オブジェクトへのバインディング

何千ものプロパティを持つ 1 つの CLR オブジェクトへのデータ バインディングは、パフォーマンスに大きな影響を及ぼします。 この影響を最小限に抑えるには、その 1 つのオブジェクトを複数の CLR オブジェクトに分割して、個々のオブジェクトのプロパティの数を減らします。 次の表では、1 つの大きな CLR オブジェクトへのデータ バインディングと複数の小さなオブジェクトへのデータ バインディングのバインディング時間とレンダリング時間を示します。

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

ItemsSource へのバインディング

ListBox に表示する従業員リストが保持されている CLR List<T> オブジェクトがあるとします。 この 2 つのオブジェクトの間に対応関係を作成するには、従業員リストを ListBoxItemsSourceプロパティにバインドします。 ここで、グループに新しい従業員が加わったとします。 バインドされた ListBox 値にその新しい従業員を挿入するには、この従業員を従業員リストに追加するだけで、その変更が自動的にデータ バインディング エンジンに認識されると思われがちです。 しかし、実際にはそうはならず、その変更は ListBox に自動的には反映されません。 これは、CLR の List<T> オブジェクトではコレクション変更イベントが自動的に発生しないからです。 ListBox に変更を反映するには、従業員リストを再作成し、もう一度 ListBoxItemsSource プロパティに割り当てる必要があります。 この解決方法で問題は解決されますが、パフォーマンスへの影響はきわめて大きくなります。 ListBoxItemsSource を新しいオブジェクトに割り当てるたびに、ListBox では最初に前の項目が削除され、リスト全体が再生成されます。 ListBox が複雑な DataTemplate にマップされている場合、パフォーマンスへの影響はさらに大きくなります。

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

次の表では、項目を 1 つ追加した場合に ListBox の更新にかかる時間を示します (UI の仮想化はオフ)。 1 行目の数字は、CLR の List<T> オブジェクトが ListBox 要素の ItemsSource にバインドされるときの経過時間を表しています。 2 行目の数字は、ObservableCollection<T>ListBox 要素の ItemsSource にバインドされるときの経過時間を表しています。 ObservableCollection<T> のデータ バインディング方法を使用すると時間を大幅に節約できることがわかります。

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

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

ItemsControl オブジェクトに IList<T> または IEnumerable のどちらをバインドするかを選択できる場合は、IList<T> オブジェクトを選択します。 IEnumerableItemsControl にバインドすると、WPF によってラッパー IList<T> オブジェクトが強制的に作成されます。これにより、追加のオブジェクトの不要なオーバーヘッドが発生するため、パフォーマンスに影響を及ぼします。

データ バインディングのためだけに CLR オブジェクトを XML に変換しない

WPF では、XML コンテンツへのデータ バインディングが可能です。ただし、XML コンテンツへのデータ バインディングは、CLR オブジェクトへのデータ バインディングに比べて低速です。 CLR オブジェクトのデータをデータ バインディングのためだけに XML に変換しないでください。

関連項目