最佳化效能:物件行為

瞭解 WPF 物件的內建行為可協助您在功能和效能之間做出正確的取捨。

不移除物件的事件處理常式可能會保持物件運作

物件傳遞給其事件的委派實際上是對該物件的參考。 因此,事件處理常式可以讓物件保持運作時間超出預期。 在對已註冊要接聽物件事件的物件執行清除時,務必要在釋放物件之前先移除該委派。 讓不需要的物件保持運行狀態會增加應用程式的記憶體使用量。 特別是當物件是邏輯樹狀結構或視覺化樹狀結構的根目錄時。

WPF 為事件引進弱式事件接聽程式模式,在來源和接聽程式之間的物件存留期關聯性難以追蹤的情況下,可能會很有用。 某些現有的 WPF 事件會使用此模式。 如果您正在以自訂事件實作物件,這個模式可能對您有用。 如需詳細資訊,請參閱弱式事件模式

有數個工具,例如 CLR 分析工具和工作集檢視器,可以提供有關指定處理序的記憶體使用量資訊。 CLR 分析工具包含許多配置設定檔的極有用檢視,包括已配置類型長條圖、配置和呼叫圖形、顯示各種層代記憶體回收的時間線和回收之後產生的 managed 堆積狀態,以及顯示每個方法配置和組件載入的呼叫樹狀結構。 如需詳細資訊,請參閱效能

相依性屬性與物件

一般而言,存取 的 DependencyObject 相依性屬性並不比存取 CLR 屬性慢。 雖然設定屬性值的效能額外負荷很小,但取得值的速度與從 CLR 屬性取得值的速度一樣快。 小小效能負荷的補償是相依性屬性可支援強大的功能,例如資料繫結、動畫、繼承和樣式。 如需詳細資訊,請參閱相依性屬性概觀

DependencyProperty 最佳化

您應該在應用程式中非常小心地定義相依性屬性。 DependencyProperty如果您只影響轉譯類型中繼資料選項,而不是其他中繼資料選項,例如 AffectsMeasure ,您應該藉由覆寫其中繼資料來將其標示為 。 如需覆寫或取得屬性中繼資料的詳細資訊,請參閱相依性屬性中繼資料

如果並非所有屬性變更都會實際上影響測量、排列與轉譯,手動讓屬性變更處理常式使測量、排列與轉譯階段失效,可能會更有效率。 比方說,您可能決定只有在值大於設定的限制時,才重新轉譯背景。 在此情況下,您的屬性變更處理常式只會在值超過設定的限制時使轉譯器失效。

使 DependencyProperty 成為可繼承不是免費的

根據預設,已註冊的相依性屬性是不可繼承的。 不過,您可以明確地使任何屬性成為可繼承。 雖然這是很有用的功能,但將屬性轉換為可繼承會影響效能,因為會增加屬性失效的時間長度。

小心使用 RegisterClassHandler

雖然呼叫 RegisterClassHandler 可讓您儲存實例狀態,但請務必注意,每個實例上都會呼叫處理常式,這可能會導致效能問題。 只有在應用程式需要您儲存實例狀態時才使用 RegisterClassHandler

在註冊期間設定 DependencyProperty 的預設值

建立 DependencyProperty 需要預設值的 時,請使用傳遞做為 參數的預設中繼資料,將值設定為 Register 的 方法 DependencyProperty 。 使用這項技術,而不是在建構函式或項目的每個執行個體上設定屬性值。

使用 Register 設定 PropertyMetadata 值

建立 DependencyProperty 時,您可以選擇使用 RegisterOverrideMetadata 方法來設定 PropertyMetadata 。 雖然您的物件可以有靜態建構函式來呼叫 OverrideMetadata ,但這不是最佳解決方案,而且會影響效能。 為了獲得最佳效能,請在呼叫 Register 期間設定 PropertyMetadata

Freezable 物件

Freezable是具有兩種狀態的特殊物件類型:解除凍結和凍結。 盡可能凍結物件可以提升應用程式的效能,並縮減其工作集。 如需詳細資訊,請參閱 Freezable 物件概觀

Changed每個 Freezable 事件都會在變更時引發。 不過,變更通知會嚴重降低應用程式效能。

請考慮下列範例,其中每個範例都會 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 可以改善其效能,因為它不再需要花費資源來維護變更通知。 下表顯示當其 IsFrozen 屬性設定為 true 時,簡單 SolidColorBrush 的大小,與不是時相比。 這假設將一個筆刷套用至 FillRectangle 個物件的 屬性。

State 大小
冷凍 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

未凍結的 Freezable 上的已變更處理常式可能會保持物件運作

物件傳遞至 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

在 指派 myBrushmyRectangle.Fill 時,指向 物件的委派 Rectangle 將會新增至 SolidColorBrush 物件的 Changed 事件。 這表示下列程式碼實際上不會讓 myRect 符合記憶體回收資格︰

myRectangle = null;
myRectangle = Nothing

在此情況下 myBrush ,它仍然保持 myRectangle 運作,並在引發事件時回呼它 Changed 。 請注意,指派 myBrushFillRectangle 屬性只會將另一個事件處理常式新增至 myBrush

清除這些類型的物件的建議方式是從 屬性中移除 BrushFill ,而屬性將會接著移除 Changed 事件處理常式。

myRectangle.Fill = null;
myRectangle = null;
myRectangle.Fill = Nothing
myRectangle = Nothing

使用者介面虛擬化

WPF 也提供自動「虛擬化」資料系結子內容之元素的變化 StackPanel 。 在此內容中,「虛擬化」一字係指一種技術,藉由這種技術,將可從較大量的資料項目,根據畫面上可見的項目來產生物件子集。 當在指定的時間內畫面上只能有幾個 UI 元素時,不論是就記憶體還是處理器而言,產生大量 UI 元素都會相當耗費資源。 VirtualizingStackPanel (透過所提供的 VirtualizingPanel 功能)會計算可見專案,並使用 ItemContainerGenerator 來自 ItemsControl (例如 ListBoxListView ) 的 ,只建立可見專案的元素。

作為效能最佳化,只有在這些項目的視覺物件顯示在螢幕上時才會產生或保持這些項目的視覺物件運作。 如果視覺物件不再位於控制項的可檢視區域中,可能會移除視覺物件。 這並不會與資料虛擬化混淆,在資料虛擬化中,資料物件不會全都在本機集合,而是視需要進行資料流處理。

下表顯示將 5000 TextBlock 個專案新增和轉譯至 StackPanelVirtualizingStackPanel 的經過時間。 在此案例中,度量代表將文字字串附加至物件屬性 ItemsControlItemsSource 面板元素顯示文字字串的時間之間的時間。

主面板 轉譯時間 (毫秒)
StackPanel 3210
VirtualizingStackPanel 46

另請參閱