專案中某 WPF 模組操作過程中 **記憶體單增不減**,使用 **ANTS Memory Profiler 5** 分析發現根因:Application.Resources 永久強參考臨時控制項 SelectionDataGrid,導致整個視覺樹無法被 GC 回收。

Application.Resources (靜態資源字典)
→ DeferredResourceReference (延遲資源參考)
→ Inflated 事件 (強參考)
→ ResourceReferenceExpression (資源參考運算式)
→ SelectionDataGrid (臨時控制項實例)
→ 整個視覺樹...
When a DeferredResourceReference reference is used by a ResourceReferenceExpression class, the internal Inflated event causes a strong reference to be held to the expression. This keeps the whole visual tree from being collected by the Garbage Collector after the references are removed from the tree if the DeferredResourceReference reference is never inflated.
DynamicResource 或 {DynamicResource ...} 建立 ResourceReferenceExpression 監聽資源變更。DeferredResourceReference (延遲載入包裝器)。DeferredResourceReference 內部 Inflated 事件 **強參考** 註冊的 ResourceReferenceExpression。DynamicResource 參考 **非共享、短生命週期** 資源 (如 DataTemplate 內動態建立的控制項)Application.Resources 或長生命週期 ResourceDictionary 中😭 該補丁並不提供下載 —— 僅透過私有支援管道 (PSS) 提供給付費客戶,公開版本 **未整合至任何 Service Pack 或累積更新**。
StaticResource 在載入時一次性解析,**不建立 ResourceReferenceExpression**,完全避開 Bug。
public partial class MyControl : UserControl
{
public MyControl()
{
InitializeComponent();
Unloaded += (s, e) =>
{
// 強制清除 DynamicResource 產生的表達式
var expr = this.GetValue(MyPropertyProperty) as ResourceReferenceExpression;
if (expr != null) expr.Dispose(); // 內部方法,需反射呼叫
};
}
}
⚠️ 方案三屬 Hack 手段,依賴內部實作細節,**升級 .NET 版本可能失效**,僅作最後手段。
// 單元測試:驗證控制項 Unloaded 後可被 GC 回收
[TestMethod]
public void Control_Should_Be_Collectable_After_Unload()
{
var control = new SelectionDataGrid();
var weakRef = new WeakReference(control);
control = null;
GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();
Assert.IsFalse(weakRef.IsAlive, "控制項應可被回收,否則存在記憶體洩漏");
}
WPF DeferredResourceReference.Inflated 事件強參考 ResourceReferenceExpression 是 **經典框架級記憶體洩漏**,微軟未公開修補。**核心規避原則:能用 StaticResource 絕不用 DynamicResource;動態資源定義在短生命週期字典中;長期專案規劃遷移至 .NET 6+**。定期以 Memory Profiler 掃描 Application.Resources 強參考鏈,可及早發現同類洩漏。