Introduction: Why C# Performance Matters
C# and the .NET platform are powerful tools for building high-load web services, desktop applications, and Unity games. However, even the most elegant code can run slowly if you don't account for the specifics of how the CLR (Common Language Runtime), Garbage Collector (GC), and JIT compilation mechanisms work.
In this article, we will break down key methods for optimizing the performance of C# applications that will help you reduce response time, decrease memory consumption, and increase system throughput. All tips are accompanied by practical code examples.
1. Memory Management and Garbage Collection (GC)
One of the main features of .NET is automatic memory management. But improper use of objects leads to frequent garbage collections, which causes application "pauses" (freezes).
1.1. Avoid Allocations in Hot Paths
Try not to create new objects inside loops or frequently called methods. Instead, use object pools or reuse instances.
// Bad: a new StringBuilder is created on each iterationfor (int i = 0; i < 1000; i++){ var sb = new StringBuilder(); sb.Append(i);}
// Good: use a pool or reusevar sbPool = new StringBuilder();for (int i = 0; i < 1000; i++){ sbPool.Clear(); sbPool.Append(i);}1.2. Use Structs Instead of Classes for Small Data
Structs are stored on the stack and do not create load on the GC. But be careful: large structs (more than 16 bytes) can be slow to copy.
public struct Point{ public int X; public int Y;}
// Now calling the method does not create an object on the heapvoid ProcessPoint(Point p) { /* ... */ }1.3. Enable Server GC Mode for Server Applications
By default, console applications use Workstation GC. For web services (ASP.NET Core), switch to Server GC in the csproj file:
<PropertyGroup> <ServerGarbageCollection>true</ServerGarbageCollection></PropertyGroup>2. Optimizing Work with LINQ and Collections
LINQ is a convenient but potentially slow tool if you don't know its "pitfalls".
2.1. Avoid Multiple Enumerations
Each call to .Count(), .ToList(), or .Any() can re-execute the query. Save the result to a variable.
// Bad: two passes over the collectionif (items.Where(x => x.IsActive).Count() > 0) { /* ... */ }var activeList = items.Where(x => x.IsActive).ToList();
// Good: one passvar activeItems = items.Where(x => x.IsActive).ToList();if (activeItems.Count > 0) { /* ... */ }2.2. Use Specialized Collections
For frequent key-based lookups, use Dictionary<TKey, TValue> instead of List<T>. For "first in, first out" operations, use Queue<T>.
// Fast lookup: O(1) instead of O(n)var dict = new Dictionary<int, string>();dict.Add(1, "one");if (dict.ContainsKey(1)) { /* ... */ }2.3. Replace LINQ with Loops in Critical Sections
For simple operations (sum, filtering), a for or foreach loop can be 2-3 times faster than LINQ.
// LINQ (slow)int sum = numbers.Sum();
// Loop (fast)int sum = 0;for (int i = 0; i < numbers.Length; i++){ sum += numbers[i];}3. Asynchronous Programming and Reducing Blocking
Proper use of async/await allows you to not block threads, which is critical for scalability.