Performance bottlenecks can disable an application’s efficiency, scalability, and user experience. Many .NET developers unknowingly fall into anti-patterns that degrade performance over time. In this article, we’ll explore the top 10 .NET performance anti-patterns, explain why they are problematic, and show how to fix them with optimized solutions.

1. Excessive Object Allocation and Garbage Collection Pressure

The Problem:

Creating too many short-lived objects leads to frequent garbage collection (GC) cycles, impacting application performance.

Fix:

  • Use object pooling for reusable objects.
  • Leverage structs over classes for small, immutable objects.
  • Reduce allocations by using Span and Memory.
  • Enable GC tuning using GCSettings.LargeObjectHeapCompactionMode when necessary.

Example:

// Instead of this:
var data = new byte[1024];// Use MemoryPool to reuse allocated memory:
var pool = MemoryPool<byte>.Shared;
using (var owner = pool.Rent(1024))
{
var memory = owner.Memory;
// Process memory here
}

2. Blocking Asynchronous Code (Sync Over Async)

The Problem:

Calling .Result or .GetAwaiter().GetResult() on asynchronous methods blocks threads and can lead to deadlocks.

Fix:

  • Always use async/await.
  • Avoid mixing synchronous and asynchronous code.

Example:

// Anti-pattern
public string GetData()
{
return GetDataAsync().Result; // Blocks thread
}

// Fix
public async Task<string> GetDataAsync()
{
return await FetchDataFromServiceAsync();
}

3. Inefficient Database Queries

The Problem:

  • N+1 query problem in ORMs like Entity Framework.
  • Not using proper indexing.
  • Querying too much data.

Fix:

  • Avoid lazy loading and Use eager loading by including all necessary related data upfront.
  • Optimize queries with pagination and indexing.
  • Profile queries using EF Core logging.

Example:

// Anti-pattern
var orders = context.Orders.ToList();
foreach (var order in orders)
{
var customer = context.Customers.Find(order.CustomerId); // N+1 issue
}

// Fix
var ordersWithCustomers = context.Orders.Include(o => o.Customer).ToList();

4. Overusing Reflection

The Problem:

Reflection incurs a significant performance cost due to metadata inspection.

Fix:

  • Use compiled expressions or source generators instead.
  • Cache reflection results instead of calling it repeatedly.

Example:

// Anti-pattern
var type = typeof(MyClass);
var property = type.GetProperty("MyProperty");
var value = property.GetValue(instance);

// Fix
var propertyDelegate = (Func<MyClass, object>)Delegate.CreateDelegate(
typeof(Func<MyClass, object>), null, type.GetProperty("MyProperty").GetMethod);
var valueOptimized = propertyDelegate(instance);

5. Using String Concatenation in Loops

The Problem:

Strings are immutable; repeated concatenation creates multiple new string objects in memory.

Fix:

  • Use StringBuilder for repeated string operations.

Example:

// Anti-pattern
string result = "";
for (int i = 0; i < 1000; i++)
{
result += i.ToString();
}

// Fix
var sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
{
sb.Append(i);
}
string optimizedResult = sb.ToString();

6. Ignoring Caching for Expensive Computations

The Problem:

Performing the same expensive computation multiple times wastes resources.

Fix:

  • Use MemoryCacheRedis, or Lazy for caching.
  • Implement output caching for repeated responses.

Example:

private static readonly MemoryCache _cache = new MemoryCache(new MemoryCacheOptions());

public string GetExpensiveData(string key)
{
if (!_cache.TryGetValue(key, out string cachedData))
{
cachedData = ComputeExpensiveData();
_cache.Set(key, cachedData, TimeSpan.FromMinutes(10));
}
return cachedData;
}

7. Not Using async Database Calls

The Problem:

Synchronous database queries block threads and reduce scalability.

Fix:

  • Use async versions of EF Core methods like ToListAsync().

Example:

// Anti-pattern
var users = context.Users.ToList(); // Blocks thread

// Fix
var users = await context.Users.ToListAsync();

8. Excessive Logging in Performance-Critical Paths

The Problem:

Logging too much in hot paths slows down execution.

Fix:

  • Use conditional logging.
  • Reduce logging verbosity in critical sections.

Example:

// Anti-pattern
_logger.LogInformation("Processing item: {Id}", item.Id);

// Fix
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug("Processing item: {Id}", item.Id);
}

9. Using LINQ Inefficiently

The Problem:

Certain LINQ operations, like ToList() before filtering, result in unnecessary memory usage.

Fix:

  • Use deferred execution by avoiding .ToList() too early.

Example:

// Anti-pattern
var filteredUsers = context.Users.ToList().Where(u => u.IsActive);

// Fix
var filteredUsers = context.Users.Where(u => u.IsActive).ToList();

10. Ignoring Async Streams for Large Data Processing

The Problem:

Loading large datasets into memory at once causes high memory consumption.

Fix:

  • Use IAsyncEnumerable to stream data.

Example:

public async IAsyncEnumerable<User> GetUsersAsync()
{
await foreach (var user in context.Users.AsAsyncEnumerable())
{
yield return user;
}
}

Key Takeaways:

  • Avoid excessive memory allocations to reduce GC overhead.
  • Embrace async programming to improve responsiveness.
  • Optimize database queries to prevent unnecessary data retrieval.
  • Use caching to minimize redundant computations.
  • Reduce logging in performance-sensitive paths.

Fixing these anti-patterns will significantly enhance the performance of your .NET applications, leading to better scalability and efficiency.

What’s Next?

Start by profiling your code using dotnet-trace or BenchmarkDotNet to detect performance bottlenecks, then apply these fixes incrementally.

🚀 Happy coding!

Link: https://medium.com/@kohzadi90/top-10-net-performance-anti-patterns-you-should-fix-today-d58f4a682340