MSDN Community Topics

Optimizing .NET Applications

Posted by JaneDoe • Sep 18, 2025 • 45 Comments

Performance is often the deciding factor between a good and great .NET application. Below are proven techniques to extract maximum efficiency from your code, runtime, and deployment environment.

1. Use Span<T> and Memory<T> for Low‑Allocation Code

These structs allow you to work with slices of memory without heap allocations.

public static int SumNumbers(ReadOnlySpan<int> numbers)
{
    int sum = 0;
    foreach (var n in numbers)
        sum += n;
    return sum;
}

// Usage
int[] data = {1,2,3,4,5};
int result = SumNumbers(data); // No allocation

2. Leverage the ValueTask Type

When an async method often completes synchronously, returning ValueTask avoids unnecessary allocations.

public ValueTask<int> GetCachedValueAsync()
{
    if (_cache.TryGetValue(out int value))
        return new ValueTask<int>(value); // Synchronous path
    return new ValueTask<int>(FetchFromDbAsync());
}

3. Enable Tiered Compilation

Let the JIT compile methods in two phases: a quick baseline followed by optimized code after enough executions.

// In your .runtimeconfig.json
{
  "runtimeOptions": {
    "configProperties": {
      "System.GC.Server": true,
      "System.Threading.ThreadPool.MinThreads": 4,
      "System.Runtime.TieredCompilation": true
    }
  }
}

4. Profile with PerfView or dotnet-trace

Identify hot paths and allocation sources before applying optimizations.

# Collect a trace
dotnet-trace collect --process-id <pid> -o trace.nettrace

# Analyze with PerfView
PerfView.exe trace.nettrace

5. Reduce Boxing & Unboxing

Prefer generic collections and structs over object when possible.

6. Use Asynchronous Streams (IAsyncEnumerable)

Stream data without buffering the entire collection.

public async IAsyncEnumerable<Record> GetRecordsAsync()
{
    await using var cmd = new SqlCommand("SELECT * FROM LargeTable", connection);
    await using var reader = await cmd.ExecuteReaderAsync();

    while (await reader.ReadAsync())
        yield return Map(reader);
}

7. Optimize Startup Time

8. Cache Frequently Used Results

Use MemoryCache or distributed caches (Redis) for expensive calculations.

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

public async Task<string> GetUserProfileAsync(Guid userId)
{
    if (_cache.TryGetValue(userId, out string profile))
        return profile;

    profile = await _db.GetUserProfileAsync(userId);
    _cache.Set(userId, profile, TimeSpan.FromMinutes(10));
    return profile;
}

9. Avoid Synchronous I/O

Blocking I/O can stall thread pool threads, reducing throughput.

10. Monitor Production Metrics

Implement OpenTelemetry and export metrics to Prometheus or Azure Monitor.

Applying these techniques will typically yield a 15‑40% performance gain depending on the workload. Always measure before and after each change.

Table of Contents