MemoryLeakDetector 1.0.0

dotnet add package MemoryLeakDetector --version 1.0.0
                    
NuGet\Install-Package MemoryLeakDetector -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="MemoryLeakDetector" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="MemoryLeakDetector" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="MemoryLeakDetector" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add MemoryLeakDetector --version 1.0.0
                    
#r "nuget: MemoryLeakDetector, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package MemoryLeakDetector@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=MemoryLeakDetector&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=MemoryLeakDetector&version=1.0.0
                    
Install as a Cake Tool

MemoryLeakDetector

🔍 A powerful .NET 8 library for detecting memory leaks through static code analysis and runtime monitoring.

Features

Static Analysis

Analyzes your C# code to find potential memory leak patterns:

  • MLD001 - Missing IDisposable implementation for classes holding disposable resources
  • MLD002 - Event handler subscriptions without unsubscriptions
  • MLD003 - Static collections that may grow unbounded
  • MLD004 - Async void methods and fire-and-forget patterns
  • MLD005 - Closure captures that may cause memory leaks
  • MLD006 - Timer instances not properly disposed

Runtime Monitoring

Monitor memory usage at runtime:

  • Memory snapshots and trend analysis
  • GC generation monitoring
  • Large Object Heap tracking
  • Memory growth detection

Installation

Get real-time warnings directly in your IDE (VS Code, Visual Studio, Rider):

dotnet add package MemoryLeakDetector.Analyzers

This shows yellow/orange squiggles (warnings, not errors) as you type:

IDE Warning Example

public class MyService
{
    private HttpClient _client = new HttpClient();  // ⚠️ MLD001: Class should implement IDisposable
    
    public async void ProcessAsync()  // ⚠️ MLD004: Async void method detected
    {
        // ...
    }
}

Option 2: CLI Tool (For CI/CD)

dotnet tool install -g MemoryLeakDetector.Cli
mld analyze ./MyProject

Option 3: Library (For Custom Integration)

dotnet add package MemoryLeakDetector
var report = MemoryLeakAnalyzer.AnalyzeProject(@"C:\MyProject");

Quick Start

Simple API Usage

using MemoryLeakDetector;

// Analyze a project directory
var report = MemoryLeakAnalyzer.AnalyzeProject(@"C:\MyProject");
Console.WriteLine(report);

// Check if project has critical issues (useful for CI/CD)
if (MemoryLeakAnalyzer.HasCriticalIssues(@"C:\MyProject"))
{
    Console.WriteLine("Found critical memory leak issues!");
    Environment.Exit(1);
}

// Analyze with options
var report = MemoryLeakAnalyzer.AnalyzeProject(@"C:\MyProject", new AnalysisOptions
{
    OutputFormat = ReportFormat.Markdown,
    SaveReportTo = "memory-report.md"
});

// Analyze a single file
foreach (var issue in MemoryLeakAnalyzer.AnalyzeFile(@"C:\MyProject\MyClass.cs"))
{
    Console.WriteLine($"[{issue.Severity}] {issue.Title}");
}

// Analyze code directly
var code = @"
public class MyService 
{
    private HttpClient _client = new HttpClient();
}";

foreach (var issue in MemoryLeakAnalyzer.AnalyzeCode(code))
{
    Console.WriteLine($"{issue.Id}: {issue.Description}");
}

CLI Usage

# Analyze a project (code + NuGet packages)
mld analyze ./MyProject

# Scan only NuGet packages
mld nuget ./MyProject

# Scan NuGet with verbose output
mld nuget ./MyProject -v

# Analyze with JSON output
mld analyze ./MyProject -f Json -o report.json

# Quick check (returns exit code for CI/CD)
mld check ./MyProject

# Show current memory info
mld memory

# Force GC
mld gc

NuGet Scanning

// Scan only NuGet packages
foreach (var issue in MemoryLeakAnalyzer.AnalyzeNuGetPackages(@"C:\MyProject"))
{
    Console.WriteLine($"[{issue.TypeName}] {issue.Title}");
    Console.WriteLine($"  {issue.Recommendation}");
}

Runtime Monitoring

using MemoryLeakDetector;
using MemoryLeakDetector.Runtime;

// Create a monitor with auto-snapshots
using var monitor = MemoryLeakAnalyzer.CreateMonitor(autoSnapshot: true);

// Or manually take snapshots
var snapshot = monitor.TakeSnapshot();
Console.WriteLine($"Memory: {snapshot.TotalManagedMemory:N0} bytes");

// Analyze trends (after collecting several snapshots)
foreach (var issue in monitor.AnalyzeTrends())
{
    Console.WriteLine($"[{issue.Severity}] {issue.Title}");
}

// Force GC and see how much was freed
var freed = MemoryLeakAnalyzer.ForceGarbageCollection();
Console.WriteLine($"Freed: {freed:N0} bytes");

Configuring Analyzer Severity

All rules are warnings by default (yellow squiggles). You can customize severity in .editorconfig:

# .editorconfig

# Disable a specific rule
dotnet_diagnostic.MLD001.severity = none

# Make a rule an error (will fail build)
dotnet_diagnostic.MLD004.severity = error

# Make a rule a suggestion (light bulb only)
dotnet_diagnostic.MLD002.severity = suggestion

Or in your .csproj:

<PropertyGroup>
  
  <WarningsAsErrors>MLD001;MLD004;MLD006</WarningsAsErrors>
  
  
  <NoWarn>MLD003</NoWarn>
</PropertyGroup>

What It Detects

🟡 Memory Leak Analyzers (Yellow/Orange - Warning)

ID Issue
MLD001 Missing IDisposable implementation
MLD002 Event handler subscription without unsubscription
MLD003 Static collection may grow unbounded
MLD004 Async void method
MLD006 Timer not disposed
MLD007 HttpClient created per request / in using
MLD008 DbContext stored in long-lived field

🔵 Performance Analyzers (Blue - Info/Suggestion)

ID Issue
PERF001 String concatenation in loop
PERF002 LINQ method in loop / Multiple enumeration
PERF003 Boxing allocation in hot path
PERF004 Regex not compiled / Static Regex.IsMatch
PERF005 Async method without await / Unnecessary async
PERF006 List created without capacity
PERF007 ContainsKey + indexer (use TryGetValue)
PERF008 Params array allocation in loop
PERF011 String.ToLower() for comparison
PERF012 Closure allocation in loop
PERF013 JsonSerializerOptions created per call

💡 Severity Levels

Color Severity Meaning
🟡 Yellow/Orange Warning Likely bug or memory leak
🔵 Blue Info Performance suggestion
💡 Light bulb Suggestion Consider improving

Quick Start

// ❌ BAD: Class holds disposable resource but doesn't implement IDisposable
public class MyService
{
    private HttpClient _client = new HttpClient();
}

// ✅ GOOD: Properly implements IDisposable
public class MyService : IDisposable
{
    private HttpClient _client = new HttpClient();
    private bool _disposed;

    public void Dispose()
    {
        if (!_disposed)
        {
            _client?.Dispose();
            _disposed = true;
        }
    }
}

MLD002: Event Handler Leaks

// ❌ BAD: Subscribes but never unsubscribes
public class MyHandler
{
    public MyHandler(EventSource source)
    {
        source.DataReceived += OnDataReceived;
    }
}

// ✅ GOOD: Properly unsubscribes
public class MyHandler : IDisposable
{
    private EventSource _source;

    public MyHandler(EventSource source)
    {
        _source = source;
        _source.DataReceived += OnDataReceived;
    }

    public void Dispose()
    {
        _source.DataReceived -= OnDataReceived;
    }
}

MLD003: Static Collection Growth

// ❌ BAD: Static collection can grow unbounded
public static class Cache
{
    private static readonly Dictionary<string, object> _cache = new();
    
    public static void Add(string key, object value) => _cache[key] = value;
}

// ✅ GOOD: Use bounded cache or MemoryCache
public static class Cache
{
    private static readonly MemoryCache _cache = new(new MemoryCacheOptions
    {
        SizeLimit = 1000
    });
}

MLD004: Async Void

// ❌ BAD: Async void can't be awaited, exceptions are unobservable
public async void ProcessDataAsync()
{
    await Task.Delay(100);
}

// ✅ GOOD: Return Task
public async Task ProcessDataAsync()
{
    await Task.Delay(100);
}

MLD005: Closure Captures

// ❌ BAD: Lambda captures 'this', preventing GC
public class MyService
{
    public void Subscribe(EventAggregator aggregator)
    {
        aggregator.Subscribe(() => this.DoWork()); // Captures 'this'
    }
}

// ✅ GOOD: Capture only what's needed
public class MyService
{
    public void Subscribe(EventAggregator aggregator)
    {
        var work = GetWorkDelegate(); // Capture minimal state
        aggregator.Subscribe(() => work());
    }
}

MLD006: Timer Not Disposed

// ❌ BAD: Timer keeps firing after object should be GC'd
public class Poller
{
    private Timer _timer;
    
    public Poller()
    {
        _timer = new Timer(_ => Poll(), null, 0, 1000);
    }
}

// ✅ GOOD: Dispose the timer
public class Poller : IDisposable
{
    private Timer _timer;
    
    public Poller()
    {
        _timer = new Timer(_ => Poll(), null, 0, 1000);
    }
    
    public void Dispose()
    {
        _timer?.Dispose();
    }
}

CI/CD Integration

GitHub Actions

- name: Check for memory leaks
  run: |
    dotnet tool install -g MemoryLeakDetector.Cli
    mld check ./src

Azure DevOps

- script: |
    dotnet tool install -g MemoryLeakDetector.Cli
    mld check ./src
  displayName: 'Memory Leak Check'

Report Formats

Console (default)

Human-readable output with colors and emojis.

JSON

Machine-readable format for integration with other tools.

Markdown

Perfect for including in PRs or documentation.

Extending

Add custom analyzers by implementing ICodeAnalyzer:

public class MyCustomAnalyzer : SyntaxAnalyzerBase
{
    public override string AnalyzerId => "CUSTOM001";
    public override string AnalyzerName => "My Custom Check";
    public override IssueCategory Category => IssueCategory.MissingDispose;

    public override IEnumerable<MemoryIssue> Analyze(SyntaxTree syntaxTree, SemanticModel? semanticModel)
    {
        // Your analysis logic here
        yield break;
    }
}

License

MIT

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 302 12/17/2025