Agent skill

optimizing-wpf-memory

Covers WPF memory optimization including Freezable patterns, common memory leak causes, and diagnostic techniques. Use when experiencing memory growth, implementing resource caching, or debugging memory issues.

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/optimizing-wpf-memory

SKILL.md

WPF Memory Optimization

1. Freezable Pattern

Why Freeze?

Benefit Description
Thread-safe Can be used across threads
No change tracking Reduces overhead
Renderer optimization Better GPU utilization

Basic Usage

csharp
// Always freeze static resources
var brush = new SolidColorBrush(Colors.Red);
brush.Freeze();

var pen = new Pen(Brushes.Black, 1);
pen.Freeze();

XAML Freeze

xml
<Window xmlns:po="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options"
        mc:Ignorable="po">
    <Window.Resources>
        <SolidColorBrush x:Key="FrozenBrush" Color="Red" po:Freeze="True"/>
    </Window.Resources>
</Window>

When Freeze Fails

csharp
if (brush.CanFreeze)
    brush.Freeze();
else
    // Has bindings or animations - cannot freeze

Modifying Frozen Objects

csharp
var clone = frozenBrush.Clone(); // Creates unfrozen copy
clone.Color = Colors.Blue;
clone.Freeze(); // Freeze again if needed

2. Common Memory Leaks

Event Handler Leaks

csharp
// ❌ LEAK: Static event holds reference
SomeStaticClass.StaticEvent += OnEvent;

// ✅ FIX: Unsubscribe in Unloaded
Unloaded += (s, e) => SomeStaticClass.StaticEvent -= OnEvent;

CompositionTarget.Rendering Leak

csharp
// ❌ LEAK: Never unsubscribed
CompositionTarget.Rendering += OnRendering;

// ✅ FIX: Always unsubscribe
Loaded += (s, e) => CompositionTarget.Rendering += OnRendering;
Unloaded += (s, e) => CompositionTarget.Rendering -= OnRendering;

Binding Without INotifyPropertyChanged

csharp
// ❌ LEAK: PropertyDescriptor retained
public string Name { get; set; } // No INPC

// ✅ FIX: Implement INPC
public string Name
{
    get => _name;
    set { _name = value; OnPropertyChanged(); }
}

DispatcherTimer Leak

csharp
// ❌ LEAK: Timer keeps running
_timer = new DispatcherTimer();
_timer.Tick += OnTick;
_timer.Start();

// ✅ FIX: Stop and cleanup
Unloaded += (s, e) =>
{
    _timer.Stop();
    _timer.Tick -= OnTick;
};

Image Resource Leak

csharp
// ❌ LEAK: Stream kept open
var bitmap = new BitmapImage(new Uri(path));

// ✅ FIX: Load immediately and release stream
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.UriSource = new Uri(path);
bitmap.EndInit();
bitmap.Freeze();

3. Diagnostic Checklist

Code Review Points

  • All static event subscriptions have unsubscribe logic
  • CompositionTarget.Rendering unsubscribed in Unloaded
  • DispatcherTimer stopped in Unloaded
  • ViewModels implement INotifyPropertyChanged
  • Images use BitmapCacheOption.OnLoad
  • Static resources are Frozen

Diagnostic Tools

Tool Purpose
VS Diagnostic Tools Real-time memory snapshots
dotMemory Detailed retention paths
PerfView GC and allocation analysis

Memory Monitor

csharp
public static class MemoryMonitor
{
    public static void LogMemory(string context)
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();

        var mem = GC.GetTotalMemory(true) / 1024.0 / 1024.0;
        Debug.WriteLine($"[{context}] Memory: {mem:F2} MB");
    }
}

4. Resource Factory Pattern

csharp
public static class FrozenResources
{
    public static SolidColorBrush CreateBrush(Color color)
    {
        var brush = new SolidColorBrush(color);
        brush.Freeze();
        return brush;
    }

    public static Pen CreatePen(Color color, double thickness)
    {
        var pen = new Pen(CreateBrush(color), thickness);
        pen.Freeze();
        return pen;
    }
}

Didn't find tool you were looking for?

Be as detailed as possible for better results