了解 WPF 物件的內建行為有助您做出功能和效能之間的正確取捨。
不移除物件的事件處理常式可能會保持物件存活
物件傳遞給其事件的委派實際上是對該物件的參考。 因此,事件處理器可以讓物件存續時間超出預期。 在對已註冊要接聽物件事件的物件執行清除時,務必要在釋放物件之前先移除該委派。 讓不需要的物件保持運行狀態會增加應用程式的記憶體使用量。 特別是當物件是邏輯樹或視覺樹的根節點時。
WPF 針對適用於來源與接聽程式之間物件存留期關係難以追蹤的情況的事件,引進了弱式事件接聽程式模式。 某些現有的 WPF 事件會使用此模式。 如果您正在以自訂事件實作物件,這個模式可能對您有用。 如需詳細資訊,請參閱弱式事件模式。
有數個工具,例如 CLR 分析工具和工作集檢視器,可以提供有關指定處理序的記憶體使用量資訊。 CLR 分析工具包含許多配置概況的極有用檢視,包括已配置類型的長條圖、配置和呼叫圖形、顯示記憶體回收的各個世代引起來的時間線和回收之後受控堆積的狀態,以及顯示每個方法配置和組件載入的呼叫樹狀結構。 如需詳細資訊,請參閱效能。
相依性屬性與物件
一般情況下,存取DependencyObject的相依性屬性與存取 CLR 屬性一樣不會更慢。 雖然設定屬性值會有小小的效能負荷,但取得值就像取得 CLR 屬性的值一樣快。 小小效能負荷的補償是相依性屬性可支援強大的功能,例如資料繫結、動畫、繼承和樣式。 如需詳細資訊,請參閱相依性屬性概觀。
DependencyProperty 優化
您應該在應用程式中非常小心地定義相依性屬性。 如果您的 DependencyProperty 僅影響渲染類型的中繼資料選項,而不是其他中繼資料選項,例如 AffectsMeasure,您應藉由覆寫中繼資料來標示。 如需覆寫或取得屬性中繼資料的詳細資訊,請參閱相依性屬性中繼資料。
如果並非所有屬性變更都會實際上影響測量、排列與轉譯,手動讓屬性變更處理常式使測量、排列與轉譯階段失效,可能會更有效率。 比方說,您可能決定只有在值大於設定的限制時,才重新渲染背景。 在此情況下,當值超過設定的限制時,您的屬性變更處理常式僅會使渲染失效。
使 DependencyProperty 成為可繼承不是免費的
根據預設,已註冊的相依性屬性是不可繼承的。 不過,您可以明確地設定任何屬性為可繼承。 雖然這是很有用的功能,但將屬性轉換為可繼承會影響效能,因為會增加屬性失效的時間長度。
小心使用 RegisterClassHandler
雖然呼叫 RegisterClassHandler 能夠讓您儲存實例狀態,但要注意處理常式會在每個實例上執行,這可能會導致效能問題。 只有在應用程式要求您儲存實例狀態時,才使用 RegisterClassHandler。
在註冊期間設定 DependencyProperty 的預設值
建立需要預設值的 DependencyProperty 時,請將值設定為傳遞給 Register 方法的預設中繼資料。 請使用這項技術,而不要在建構函式或元素的每個實例中設定屬性值。
使用 Register 設定 PropertyMetadata 值
建立 DependencyProperty 時,您可以選擇使用 Register 或 OverrideMetadata 方法來設定 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可以提升效能,因為不再需要消耗資源維護變更通知。 下列表格顯示簡單 SolidColorBrush 的大小,當其 IsFrozen 屬性設定為 true 時,與未設定時相比。 這假設將一個筆刷套用至十個 Rectangle 物件的 Fill 屬性。
| 州 | 大小 |
|---|---|
| Frozen 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 也會在內部連結事件。 例如,所有以 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。
建議清除這些類型物件的方法是從 Fill 屬性中移除 Brush,這將進而移除 Changed 事件處理程式。
myRectangle.Fill = null;
myRectangle = null;
myRectangle.Fill = Nothing
myRectangle = Nothing
使用者介面虛擬化
WPF 也提供 StackPanel 元素的變體,可自動「虛擬化」資料繫結的子內容。 在此內容中,「虛擬化」一字係指一種技術,藉由這種技術,將可從較大量的資料項目,根據畫面上可見的項目來產生物件子集。 當在指定的時間內畫面上只能有幾個 UI 元素時,不論是就記憶體還是處理器而言,產生大量 UI 元素都會相當耗費資源。
作為效能最佳化,只有當這些項目的視覺物件顯示在螢幕上時,才會產生或保持它們的運作。 如果視覺物件不再位於控制項的可檢視區域中,可能會移除視覺物件。 這不應與資料虛擬化混淆,在資料虛擬化中,資料物件可能並不全都存在於本機集合中,而是根據需求進行串流傳輸。
下列表格顯示將 5000 個 TextBlock 元素新增至 StackPanel 和 VirtualizingStackPanel 並轉譯的經過時間。 在此情境中,測量代表的是從將文字字串附加到 ItemsSource 物件的 ItemsControl 屬性,到面板元素顯示此文字字串時之間的時間。
| 主面板 | 轉譯時間 (毫秒) |
|---|---|
| StackPanel | 3210 |
| VirtualizingStackPanel | 46 |
另請參閱
- 最佳化 WPF 應用程式效能
- 應用程式效能的規劃
- 運用硬體優勢
- 版面配置與設計
- 2D 圖形和影像
- 應用程式資源
- 文字
- 資料繫結
- 其他效能建議