WPF オブジェクトの組み込み動作を理解することは、機能とパフォーマンスの間で適切なトレードオフを行うのに役立ちます。
オブジェクトのイベント ハンドラーを削除しないと、オブジェクトが存続する可能性がある
オブジェクトがそのイベントに渡すデリゲートは、実質的にそのオブジェクトへの参照です。 そのため、イベント ハンドラーは、オブジェクトを想定よりも長く存続させることができます。 オブジェクトのイベントをリッスンするために登録されているオブジェクトのクリーンアップを実行する場合は、オブジェクトを解放する前にそのデリゲートを削除することが不可欠です。 不要なオブジェクトを維持すると、アプリケーションのメモリ使用量が増加します。 これは、オブジェクトが論理ツリーまたはビジュアル ツリーのルートである場合に特に当てはまります。
WPF では、ソースとリスナーの間のオブジェクトの有効期間の関係を追跡するのが困難な状況で役立つ可能性があるイベントの弱いイベント リスナー パターンが導入されています。 一部の既存の WPF イベントでは、このパターンが使用されます。 カスタム イベントを使用してオブジェクトを実装する場合は、このパターンが使用されている可能性があります。 詳細については、「 弱いイベント パターン」を参照してください。
CLR プロファイラーやワーキング セット ビューアーなど、指定されたプロセスのメモリ使用量に関する情報を提供できるツールがいくつかあります。 CLR Profiler には、割り当てプロファイルの非常に便利なビューが多数含まれています。これには、割り当てられた型のヒストグラム、割り当てグラフと呼び出しグラフ、さまざまな世代のガベージ コレクションと、それらのコレクション後のマネージド ヒープの結果の状態を示すタイム ライン、メソッドごとの割り当てとアセンブリの読み込みを示す呼び出しツリーが含まれます。 詳細については、「 パフォーマンス」を参照してください。
依存関係プロパティとオブジェクト
一般に、 DependencyObject の依存関係プロパティへのアクセスは、CLR プロパティへのアクセスよりも遅くはありません。 プロパティ値を設定するためのパフォーマンスオーバーヘッドは小さくなりますが、値の取得は CLR プロパティから値を取得するのと同じくらい高速です。 パフォーマンスのオーバーヘッドが小さいのは、依存関係プロパティがデータ バインディング、アニメーション、継承、スタイル設定などの堅牢な機能をサポートするという事実です。 詳細については、「依存関係プロパティの概要」を参照してください。
DependencyProperty の最適化
アプリケーションで依存関係プロパティを非常に慎重に定義する必要があります。 DependencyPropertyが、AffectsMeasureなどの他のメタデータ オプションではなく、レンダリングの種類のメタデータ オプションにのみ影響する場合は、そのメタデータをオーバーライドしてそのようにマークする必要があります。 プロパティ メタデータのオーバーライドまたは取得の詳細については、「 依存関係プロパティ メタデータ」を参照してください。
すべてのプロパティ変更が実際にメジャー、配置、およびレンダリングに影響を与えるわけではない場合は、プロパティ変更ハンドラーでメジャーの無効化、配置、およびレンダリングのパスを手動で行う方が効率的な場合があります。 たとえば、値が設定された制限を超える場合にのみ、背景を再レンダリングすることができます。 この場合、プロパティ変更ハンドラーは、値が設定された制限を超えた場合にのみレンダリングを無効にします。
DependencyProperty を継承可能にすることは無料ではありません
既定では、登録済みの依存関係プロパティは継承できません。 ただし、任意のプロパティを明示的に継承可能にすることができます。 これは便利な機能ですが、プロパティを継承可能に変換すると、プロパティの無効化の時間を長くすることでパフォーマンスに影響します。
RegisterClassHandler を慎重に使用する
RegisterClassHandlerを呼び出すとインスタンスの状態を保存できますが、すべてのインスタンスでハンドラーが呼び出され、パフォーマンスの問題が発生する可能性があることに注意することが重要です。 アプリケーションでインスタンスの状態を保存する必要がある場合にのみ、 RegisterClassHandler を使用します。
登録時に DependencyProperty の既定値を設定する
既定値を必要とするDependencyPropertyを作成する場合は、パラメーターとして渡された既定のメタデータを使用して、RegisterのDependencyProperty メソッドに値を設定します。 コンストラクターまたは要素の各インスタンスでプロパティ値を設定するのではなく、この手法を使用します。
Register を使用して PropertyMetadata 値を設定する
DependencyPropertyを作成するときは、PropertyMetadataメソッドまたはRegisterメソッドを使用してOverrideMetadataを設定できます。 OverrideMetadataを呼び出す静的コンストラクターをオブジェクトに含めることができますが、これは最適なソリューションではなく、パフォーマンスに影響します。 最適なパフォーマンスを得るために、PropertyMetadataへの呼び出し中にRegisterを設定します。
フリーズ可能なオブジェクト
Freezable は、2 つの状態 (unfrozen と frozen) を持つ特殊な種類のオブジェクトです。 可能な限りオブジェクトを固定すると、アプリケーションのパフォーマンスが向上し、そのワーキング セットが減少します。 詳細については、「Freezable オブジェクトの概要」を参照してください。
各 Freezable には、変更されるたびに発生する Changed イベントがあります。 ただし、変更通知は、アプリケーションのパフォーマンスに関してコストがかかります。
各 Rectangle が同じ Brush オブジェクトを使用する次の例を考えてみましょう。
rectangle_1.Fill = myBrush;
rectangle_2.Fill = myBrush;
rectangle_3.Fill = myBrush;
// ...
rectangle_10.Fill = myBrush;
rectangle_1.Fill = myBrush
rectangle_2.Fill = myBrush
rectangle_3.Fill = myBrush
' ...
rectangle_10.Fill = myBrush
既定では、WPF は、SolidColorBrush オブジェクトのChanged プロパティを無効にするために、Rectangle オブジェクトのFill イベントのイベント ハンドラーを提供します。 この場合、 SolidColorBrush が Changed イベントを発生させるたびに、各 Rectangleのコールバック関数を呼び出す必要があります。これらのコールバック関数呼び出しの累積により、パフォーマンスが大幅に低下します。 さらに、この時点でハンドラーを追加および削除するのは非常にパフォーマンスが高くなります。そのためには、アプリケーションがリスト全体を走査する必要があるためです。 アプリケーション シナリオで SolidColorBrushが変更されない場合は、 Changed イベント ハンドラーを不必要に維持するコストが発生します。
Freezableを凍結すると、変更通知を維持するためにリソースを消費する必要がなくなったため、パフォーマンスが向上する可能性があります。 次の表は、SolidColorBrush プロパティが IsFrozen に設定されている場合の単純なtrue
のサイズを、そうでない場合と比較して示しています。 これは、10 個のFill オブジェクトの Rectangle プロパティに 1 つのブラシを適用することを前提としています。
状態 | サイズ |
---|---|
凍結 SolidColorBrush | 212 バイト |
凍結されていない SolidColorBrush | 972 バイト |
次のコード サンプルは、この概念を示しています。
Brush frozenBrush = new SolidColorBrush(Colors.Blue);
frozenBrush.Freeze();
Brush nonFrozenBrush = new SolidColorBrush(Colors.Blue);
for (int i = 0; i < 10; i++)
{
// Create a Rectangle using a non-frozed Brush.
Rectangle rectangleNonFrozen = new Rectangle();
rectangleNonFrozen.Fill = nonFrozenBrush;
// Create a Rectangle using a frozed Brush.
Rectangle rectangleFrozen = new Rectangle();
rectangleFrozen.Fill = frozenBrush;
}
Dim frozenBrush As Brush = New SolidColorBrush(Colors.Blue)
frozenBrush.Freeze()
Dim nonFrozenBrush As Brush = New SolidColorBrush(Colors.Blue)
For i As Integer = 0 To 9
' Create a Rectangle using a non-frozed Brush.
Dim rectangleNonFrozen As New Rectangle()
rectangleNonFrozen.Fill = nonFrozenBrush
' Create a Rectangle using a frozed Brush.
Dim rectangleFrozen As New Rectangle()
rectangleFrozen.Fill = frozenBrush
Next i
Unfrozen Freezables の変更されたハンドラーがオブジェクトを維持する可能性がある
オブジェクトが Freezable オブジェクトの Changed イベントに渡すデリゲートは、実質的にそのオブジェクトへの参照です。 そのため、 Changed イベント ハンドラーは、オブジェクトを想定よりも長く存続させることができます。 Freezable オブジェクトのChanged イベントをリッスンするために登録されているオブジェクトのクリーンアップを実行する場合は、オブジェクトを解放する前にそのデリゲートを削除することが不可欠です。
WPF では、 Changed イベントも内部的にフックされます。 たとえば、 Freezable を値として受け取る依存関係プロパティはすべて、 Changed イベントを自動的にリッスンします。 Fillを受け取る Brush プロパティは、この概念を示しています。
Brush myBrush = new SolidColorBrush(Colors.Red);
Rectangle myRectangle = new Rectangle();
myRectangle.Fill = myBrush;
Dim myBrush As Brush = New SolidColorBrush(Colors.Red)
Dim myRectangle As New Rectangle()
myRectangle.Fill = myBrush
myBrush
へのmyRectangle.Fill
の割り当て時に、Rectangle オブジェクトを指すデリゲートが、SolidColorBrush オブジェクトのChanged イベントに追加されます。 つまり、次のコードは実際には myRect
をガベージ コレクションの対象にはしません。
myRectangle = null;
myRectangle = Nothing
この場合、myBrush
は依然として myRectangle
を維持しており、Changed イベントが発生したときにコールバックします。 新しいmyBrush
のFill プロパティにRectangleを割り当てると、myBrush
に別のイベント ハンドラーが追加されるだけであることに注意してください。
これらの種類のオブジェクトをクリーンアップするには、Brush プロパティからFillを削除することをお勧めします。これにより、Changed イベント ハンドラーが削除されます。
myRectangle.Fill = null;
myRectangle = null;
myRectangle.Fill = Nothing
myRectangle = Nothing
ユーザー インターフェイスの仮想化
WPF には、データ バインドされた子コンテンツを自動的に "仮想化" する StackPanel 要素のバリエーションも用意されています。 このコンテキストでは、仮想化という単語は、画面上に表示される項目に基づいて、より多くのデータ項目からオブジェクトのサブセットを生成する手法を指します。 メモリとプロセッサの両方に関しては、特定の時点で少数しか画面に表示できない場合に多数の UI 要素を生成することが集中的に行われます。 VirtualizingStackPanel(VirtualizingPanelによって提供される機能を通じて)は、表示される項目を計算し、ItemContainerGenerator (ItemsControlやListBoxなど) のListViewを使用して、表示される項目の要素のみを作成します。
パフォーマンスの最適化として、これらの項目のビジュアル オブジェクトは、画面上に表示されている場合にのみ生成または維持されます。 コントロールの表示可能領域に表示されなくなったら、ビジュアル オブジェクトが削除される可能性があります。 これはデータ仮想化と混同しないでください。データ オブジェクトがすべてローカル コレクションに存在するわけではないので、必要に応じてストリーミングされます。
次の表は、TextBlockとStackPanelに 5000 VirtualizingStackPanel要素を追加してレンダリングする経過時間を示しています。 このシナリオでは、測定値は、パネル要素がテキスト文字列を表示する時刻に、ItemsSource オブジェクトのItemsControl プロパティにテキスト文字列をアタッチする時間を表します。
ホスト パネル | レンダリング時間 (ミリ秒) |
---|---|
StackPanel | 3210 |
VirtualizingStackPanel | 46 |
こちらも参照ください
.NET Desktop feedback