⚡ Zero Allocation Until You Log
Logging with string interpolation allocates memory even when log level is off. Interpolated string handlers defer formatting until needed.
❌ Regular Interpolation (Always Allocates)
// Even if log level is Error and this is Debug
// string is still allocated!
_logger.LogDebug($"User {userId} logged in at {DateTime.Now}");
// 2 allocations: string + DateTime.ToString()
✅ LoggerMessage (No Allocation)
// No string allocation if log level disabled
_logger.LogDebug("User {UserId} logged in at {Time}", userId, DateTime.Now);
// Parameters passed directly, formatted only if needed
📝 Custom Interpolated Handler
[InterpolatedStringHandler]
public ref struct DebugLogHandler
{
private readonly bool _enabled;
private StringBuilder? _builder;
public DebugLogHandler(int literalLength, int formattedCount, bool enabled, out bool shouldAppend)
{
_enabled = enabled;
shouldAppend = enabled;
if (enabled)
{
_builder = new StringBuilder(literalLength + formattedCount * 8);
}
}
public void AppendLiteral(string s)
{
if (_enabled) _builder!.Append(s);
}
public void AppendFormatted(T value)
{
if (_enabled) _builder!.Append(value);
}
internal string GetFormattedString() => _builder?.ToString() ?? ";
}
public static class Debug
{
public static void WriteLine(DebugLogHandler handler)
{
if (Debugger.IsAttached)
{
Console.WriteLine(handler.GetFormattedString());
}
}
}
// Usage - no allocation if debugger not attached!
Debug.WriteLine($"Processing {userId} at {DateTime.Now}");
✅ Built-in Logger Support
// .NET 6+ ILogger supports this automatically
_logger.LogDebug("User {UserId} logged in", userId);
// Even better: LoggerMessage.Define (source generator)
private static readonly Action _userLoggedIn =
LoggerMessage.Define(
LogLevel.Debug,
EventIds.UserLoggedIn,
"User {UserId} logged in");
// Usage - ultra-efficient
_userLoggedIn(_logger, userId, null);
💡 Performance Impact
- Regular $”{value}”: Always allocates (~100ns + allocation)
- Logger with params: Allocates only if log level enabled
- LoggerMessage.Define: Zero allocation overhead
- High-frequency logs: Can save millions of allocations
“20,000 debug logs per second in development. GC was killing performance. Switched to LoggerMessage.Define. GC pressure dropped 90%. Production logs are free when disabled.”
