CodeBlanks Logo Icon CodeBlanks

LoggerMessage

C# course · Advanced · Lesson 75/83

Learn high-performance logging with LoggerMessage.Define.

LoggerMessage.Define

Every LogInformation("...") call allocates a string at runtime. In hot paths this is costly. LoggerMessage.Define compiles the message template once and returns a reusable delegate.

Example: private static readonly Action<ILogger, string> _greet = LoggerMessage.Define<string>(LogLevel.Information, 0, "Merhaba {User}");. Then _greet(logger, "Ayşe") does not re-parse the template on each call; it runs a ready delegate.

Exercise High-performance logging with LoggerMessage

Define a non-generic ILogger interface (void LogInformation(string message)) and an ILogger<T> : ILogger interface. ConsoleLogger<T> implements ILogger<T> and calls Console.WriteLine("[INF] " + message). In a static LoggerMessage class, DefineInformation(string message) returns Action<ILogger, string>; the returned delegate replaces the {Arg} placeholder with the second argument and calls LogInformation. In Main, create a ConsoleLogger<Program>, get the delegate via DefineInformation("Merhaba {Arg}"), and call greet(logger, "Ayşe").

Show example solution
Define interface ILogger { void LogInformation(string message); } and interface ILogger<T> : ILogger { }. Write class ConsoleLogger<T> : ILogger<T> { public void LogInformation(string message) => Console.WriteLine("[INF] " + message); }. Write static class LoggerMessage { public static Action<ILogger, string> DefineInformation(string message) => (logger, arg) => logger.LogInformation(message.Replace("{Arg}", arg)); }. In Main: var logger = new ConsoleLogger<Program>(); var greet = LoggerMessage.DefineInformation("Merhaba {Arg}"); greet(logger, "Ayşe");
Example solution:
using System;

public interface ILogger
{
    void LogInformation(string message);
}

public interface ILogger<T> : ILogger { }

public class ConsoleLogger<T> : ILogger<T>
{
    public void LogInformation(string message) =>
        Console.WriteLine("[INF] " + message);
}

public static class LoggerMessage
{
    public static Action<ILogger, string> DefineInformation(string message) =>
        (logger, arg) => logger.LogInformation(message.Replace("{Arg}", arg));
}

class Program
{
    static void Main()
    {
        var logger = new ConsoleLogger<Program>();
        var greet = LoggerMessage.DefineInformation("Merhaba {Arg}");
        greet(logger, "Ayşe");
    }
}

Reuse the delegate

The delegate from Define is stored in a static readonly field. So it is built once at startup, and each call uses the same object. Template parsing happens a single time.

In hot paths (say, logs inside loops) this gives a measurable speedup. In rarely called spots a direct LogInformation is fast enough; over-engineering should be avoided.

Test yourself

  1. Complete the Define call

    var greet = LoggerMessage.____("Merhaba {User}");
    Show answer
    var greet = LoggerMessage.DefineInformation("Merhaba {User}");
  2. Why prefer LoggerMessage.Define?

    • To write shorter code
    • To color logs
    • To capture only errors
    • To avoid allocating a string on every call
    Show answer

    To avoid allocating a string on every call — Correct. Define compiles the template once, avoiding repeated allocation.

  3. Match LoggerMessage concepts

    Define
    Method that compiles the template
    Action<ILogger, T>
    Returned delegate
    static readonly
    Stores the delegate
    {Arg}
    Placeholder

All lessons in Advanced