Skip to content

Bits of .NET

Daily micro-tips for C#, SQL, performance, and scalable backend engineering.

  • Asp.Net Core
  • C#
  • SQL
  • JavaScript
  • CSS
  • About
  • ErcanOPAK.com
  • No Access
  • Privacy Policy
Asp.Net Core / C#

.NET Core: Fix Memory Leaks by Understanding IDisposable and Using Patterns

- 01.02.26 | 01.02.26 - ErcanOPAK

Your .NET application’s memory usage grows infinitely? You’re probably not disposing resources properly. Here’s the complete guide.

The Memory Leak – Classic Example:

// WRONG: Memory leak
public async Task ProcessOrdersAsync()
{
    var dbContext = new ApplicationDbContext();
    var orders = await dbContext.Orders.ToListAsync();
    
    // Process orders...
    
    // dbContext never disposed = connection stays open = memory leak
}

// After 1000 requests: 1000 open DB connections
// Result: "Connection pool exhausted" errors

Solution 1 – Using Block (Recommended):

public async Task ProcessOrdersAsync()
{
    using (var dbContext = new ApplicationDbContext())
    {
        var orders = await dbContext.Orders.ToListAsync();
        // Process orders...
    } // dbContext.Dispose() called automatically here
}

// Modern C# 8+ syntax (even cleaner)
public async Task ProcessOrdersAsync()
{
    using var dbContext = new ApplicationDbContext();
    var orders = await dbContext.Orders.ToListAsync();
    // Process orders...
} // Disposed at end of method

Why This Works:
‘using’ statement ensures Dispose() is called even if exceptions occur. It compiles to:

ApplicationDbContext dbContext = null;
try
{
    dbContext = new ApplicationDbContext();
    // Your code
}
finally
{
    dbContext?.Dispose();  // Always runs
}

Common Leak Sources in .NET Core:

// LEAK 1: HttpClient without disposal
public async Task GetDataAsync(string url)
{
    var client = new HttpClient();  // WRONG
    return await client.GetStringAsync(url);
}

// FIX: Use IHttpClientFactory
public class MyService
{
    private readonly IHttpClientFactory _httpClientFactory;
    
    public MyService(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }
    
    public async Task GetDataAsync(string url)
    {
        using var client = _httpClientFactory.CreateClient();
        return await client.GetStringAsync(url);
    }
}

// Register in Program.cs
builder.Services.AddHttpClient();

LEAK 2: Event Handlers Not Unsubscribed:

// WRONG: Memory leak
public class OrderService
{
    public OrderService(IEventPublisher publisher)
    {
        publisher.OrderCreated += OnOrderCreated;
        // OrderService can never be garbage collected
        // even after it's "done" because publisher keeps reference
    }
    
    private void OnOrderCreated(object sender, OrderEventArgs e)
    {
        // Handle event
    }
}

// FIX: Unsubscribe in Dispose
public class OrderService : IDisposable
{
    private readonly IEventPublisher _publisher;
    
    public OrderService(IEventPublisher publisher)
    {
        _publisher = publisher;
        _publisher.OrderCreated += OnOrderCreated;
    }
    
    private void OnOrderCreated(object sender, OrderEventArgs e)
    {
        // Handle event
    }
    
    public void Dispose()
    {
        _publisher.OrderCreated -= OnOrderCreated;
    }
}

LEAK 3: Static Event Handlers:

// WRONG: Permanent memory leak
public class MyClass
{
    public MyClass()
    {
        AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
        // Static event keeps MyClass alive FOREVER
    }
}

// FIX: Use WeakEventManager or unsubscribe
public class MyClass : IDisposable
{
    public MyClass()
    {
        AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
    }
    
    public void Dispose()
    {
        AppDomain.CurrentDomain.ProcessExit -= OnProcessExit;
    }
    
    private void OnProcessExit(object sender, EventArgs e)
    {
        // Cleanup
    }
}

Implementing IDisposable Correctly:

public class ResourceManager : IDisposable
{
    private FileStream _fileStream;
    private HttpClient _httpClient;
    private bool _disposed = false;

    public ResourceManager()
    {
        _fileStream = new FileStream("data.txt", FileMode.Open);
        _httpClient = new HttpClient();
    }

    // Public dispose method
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this); // Prevent finalizer from running
    }

    // Protected dispose method
    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return;

        if (disposing)
        {
            // Dispose managed resources
            _fileStream?.Dispose();
            _httpClient?.Dispose();
        }

        // Free unmanaged resources here (if any)
        // Example: CloseHandle(ptr)

        _disposed = true;
    }

    // Finalizer (only if you have unmanaged resources)
    ~ResourceManager()
    {
        Dispose(false);
    }
}

Detecting Memory Leaks – Tools:

// Visual Studio Memory Profiler
// 1. Debug → Performance Profiler
// 2. Select ".NET Object Allocation"
// 3. Run your app
// 4. Look for objects that never get collected

// dotnet-counters (CLI tool)
dotnet-counters monitor --process-id  --counters System.Runtime

// Watch these metrics:
// - GC Heap Size (should not grow infinitely)
// - Gen 2 Collections (frequent = potential leak)
// - Exception Count (exceptions prevent disposal)

ASP.NET Core Scoped Services Pattern:

// Register disposable services properly
builder.Services.AddScoped<IOrderService, OrderService>();
// Scoped = one instance per HTTP request
// Automatically disposed at end of request

// Inside controller
public class OrderController : ControllerBase
{
    private readonly IOrderService _orderService;
    
    public OrderController(IOrderService orderService)
    {
        _orderService = orderService;
        // Don't dispose! DI container handles it
    }
}

Related posts:

C# — Stopwatch Is More Accurate Than DateTime

C#: Async/Await Best Practices to Avoid Deadlocks and Performance Issues

C#: Use Lazy for Expensive Object Initialization

Post Views: 10

Post navigation

.NET Core: Reduce Docker Image Build Time with Layer Caching Optimization
SQL Server: Find Slow Queries with Query Store (No Third-Party Tools Needed)

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

July 2026
M T W T F S S
 12345
6789101112
13141516171819
20212223242526
2728293031  
« Jun    

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (960)
  • How to add default value for Entity Framework migrations for DateTime and Bool (899)
  • How to make theater mode the default for Youtube (859)
  • Get the First and Last Word from a String or Sentence in SQL (840)
  • How to select distinct rows in a datatable in C# (815)
  • How to enable, disable and check if Service Broker is enabled on a database in SQL Server (600)
  • Add Constraint to SQL Table to ensure email contains @ (583)
  • Average of all values in a column that are not zero in SQL (545)
  • How to use Map Mode for Vertical Scroll Mode in Visual Studio (512)
  • Find numbers with more than two decimal places in SQL (460)

Recent Posts

  • C#: Use Using Statements for Resource Management
  • C#: Use Lambda Expressions for Concise Code
  • SQL: Use GROUP BY for Data Aggregation
  • .NET Core: Master Routing for Clean URLs
  • Git: Use Reset to Undo Local Changes
  • Ajax: Use Axios for HTTP Requests
  • JavaScript: Understand Hoisting
  • HTML: Use Web Storage for Client-Side Data
  • CSS: Use Filter Effects for Visual Magic
  • Windows 11: Unlock God Mode for All Settings

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (960)
  • How to add default value for Entity Framework migrations for DateTime and Bool (899)
  • How to make theater mode the default for Youtube (859)
  • Get the First and Last Word from a String or Sentence in SQL (840)
  • How to select distinct rows in a datatable in C# (815)

Recent Posts

  • C#: Use Using Statements for Resource Management
  • C#: Use Lambda Expressions for Concise Code
  • SQL: Use GROUP BY for Data Aggregation
  • .NET Core: Master Routing for Clean URLs
  • Git: Use Reset to Undo Local Changes

Social

  • ErcanOPAK.com
  • GoodReads
  • LetterBoxD
  • Linkedin
  • The Blog
  • Twitter
© 2026 Bits of .NET | Built with Xblog Plus free WordPress theme by wpthemespace.com