Is there a technique for using DebugFormat() with args that are expensive to construct?

Viewed 475

I'm a big fan of log4net and log4j's "format" API for logging messages, which avoids the cost of calling ToString() on arguments if the necessary log level is not enabled.

But there are times when one or more of the arguments I'd use is not a simple object, it needs to be constructed in some way. For example, like this:

logger.DebugFormat("Item {0} not found in {1}",
        itemID,
        string.Join(",", items.Select(i => <you get the idea>))
       );

Is there a technique to encapsulate the second argument (the Join expression) such that it won't be executed unless DebugFormat decides that it should be (like it does for the ToString of the first argument)?

It feels like a lambda or func or something should be able to help here, but I'm fairly new to C# and I can't quite put my finger on it.

1 Answers

You can create extension method or wrapper class, but it's not easy to get satisfying syntax, because you want some parameters (itemID in your example) to be stated explicitly, and some to be resolved only if necessary. But you cannot pass anonymous function as object. Instead I'd use another solution which does not require extension methods or wrappers. Create class like this:

public sealed class Delayed {
    private readonly Lazy<object> _lazy;
    public Delayed(Func<object> func) {
        _lazy = new Lazy<object>(func, false);
    }

    public override string ToString() {
        var result = _lazy.Value;
        return result != null ? result.ToString() : "";
    }
}

This accepts function which returns object in constructor and will not call this function until ToString() is called, which as you know is called by log4net only if necessary (if such debugging level is enabled). Then use like this:

logger.DebugFormat("Item {0} not found in {1}",
    itemID,
    new Delayed(() => string.Join(",", items.Select(i => <you get the idea>)))
   );
Related