Performance and clean code in .NET
Clean code and performance are not at odds — readable code is easier to optimize, and deliberate performance choices need not obscure it. This lesson gathers the practices we follow in ProfessNet .NET services.
Measure before you optimize
Don't guess — profile. Micro-optimizations in code that isn't a bottleneck are a waste of time and readability.
ProfessNet standard: we optimize based on measurement (a profiler, BenchmarkDotNet), not hunches. Readability > a clever trick, until the measurement says otherwise.
Avoid unnecessary allocations
In hot paths we watch out for heap allocations — they are what burdens the GC.
// bad — string concat in a loop allocates every time
var report = "";
foreach (var p in probes)
report += p.Host + "\n";
// good — StringBuilder
var sb = new StringBuilder();
foreach (var p in probes)
sb.Append(p.Host).Append('\n');
var report = sb.ToString();
For copy-free parsing we use Span<T> / ReadOnlySpan<char>:
ReadOnlySpan<char> span = line.AsSpan();
var forest = span[..span.IndexOf(',')]; // no allocation of a new string
Async streaming — IAsyncEnumerable
We don't load large sets entirely into memory. We stream them.
public async IAsyncEnumerable<Probe> StreamProbesAsync(
[EnumeratorCancellation] CancellationToken ct = default)
{
await foreach (var probe in _repository.QueryAsync(ct))
{
yield return probe;
}
}
LINQ deliberately
LINQ is readable, but it iterates the collection — don't materialize multiple
times and don't call .Count() on an IEnumerable if you have .Count on a
list.
// bad — double iteration (Where + Any)
if (probes.Where(p => p.IsActive).Any()) { }
// good — a single pass
if (probes.Any(p => p.IsActive)) { }
// materialize once if you use the result multiple times
var active = probes.Where(p => p.IsActive).ToList();
Clean code — short methods, clear names
// bad — the method does too much, magic numbers
public void P(Probe x)
{
if (x.S == 0 && x.T > 1440) { /* ... */ }
}
// good — expressing intent
private const int StaleAfterMinutes = 1440;
public void MarkStaleIfNeeded(Probe probe)
{
if (probe.IsOffline && probe.MinutesSinceLastSeen > StaleAfterMinutes)
{
probe.MarkStale();
}
}
| Practice | Benefit |
|---|---|
StringBuilder in a loop | fewer allocations |
Span<T> when parsing | no string copies |
IAsyncEnumerable | constant memory overhead |
| a single LINQ pass | fewer iterations |
| named constants | no magic numbers |
Tip:
sealedon classes that aren't inherited gives the JIT an opportunity to optimize virtual calls — and documents intent.
Measure before you optimize; cut allocations in hot paths; stream large data; write short, named methods. Performant .NET is the sum of deliberate, small decisions — not heroic rewrites at the end of the project.