Custom String Interpolation Handler
- When using string interpolation, the default interpolated string handler process placeholders similar to
String.Format
. Each placeholder is formatted as text, and then the components are concatenated to form the resulting string. - C# 10 adds support for a custom
interpolated string handler
. - An interpolated string handler is a type that processes the placeholder expression in an interpolated string.
Custom Handler
To implement custom interpolated string handler follow these steps
System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute
applied to the type.- A constructor that has two int parameters, literalLength and formatCount. (More parameters are allowed).
- A public
AppendLiteral
method with the signature - A generic public
AppendFormatted
method
[InterpolatedStringHandler]
public struct LogInterpolatedStringHandler
{
StringBuilder builder;
public LogInterpolatedStringHandler(int literalLength, int formattedCount)
{
builder = new StringBuilder(literalLength);
}
public void AppendLiteral(string s)
{
Console.WriteLine($"Append Literal {s}");
builder.Append(s);
}
public void AppendFormatted<T>(T t)
{
Console.WriteLine($"Append Formatted {t}");
builder.Append(t.ToString());
}
internal string GetFormattedText() => builder.ToString();
}
A simple logger class that logs the message to console. When interpolated string is passed, the method with interpolated string handler is executed
public class Logger
{
public void LogMessage(string msg)
{
Console.WriteLine("LogMessage string version");
Console.WriteLine(msg);
}
public void LogMessage(LogInterpolatedStringHandler builder)
{
Console.WriteLine("LogMessage interpolated string version");
Console.WriteLine(builder.GetFormattedText());
}
}
var logger = new Logger();
logger.LogMessage("Critical messsage");
Console.WriteLine("");
logger.LogMessage($"Critical messsage at {DateTime.Now}");
Pass Additional Parameters
You can pass additional parameters to Interpolated String Handler
public LogInterpolatedStringHandler(int literalLength, int formattedCount, Logger logger, LogLevel logLevel)
{
}
Update the method so that compiler passes additional parameter to the constructor parameter
public void LogMessage(LogLevel level, [InterpolatedStringHandlerArgument("", "level")] LogInterpolatedStringHandler builder)
{
Console.WriteLine(builder.GetFormattedText());
}
InterpolatedStringHandlerArgument
specifies the list of arguments that follow the required literalLength
and formattedCount
parameters in the constructor.
The compiler substitutes the value of the Logger object represented by this
for the logger
parameter in the constructor.
The compiler substitutes the value of level
for the logLevel
parameter in the constructor.
In this example, you will log the message only if the EnabledLevel
is greater than parameter loglevel
. Also the process to construct the string from interpolated string expression is not done if the EnabledLevel
is lesser than logLevel
public enum LogLevel
{
Off,
Critical,
Error,
Warning,
Information,
Trace
}
[InterpolatedStringHandler]
public struct LogInterpolatedStringHandler
{
StringBuilder builder;
private readonly bool enabled;
public LogInterpolatedStringHandler(int literalLength, int formattedCount, Logger logger, LogLevel logLevel)
{
enabled = logger.EnabledLevel >= logLevel;
builder = new StringBuilder(literalLength);
}
public void AppendLiteral(string s)
{
if(!enabled) return;
Console.WriteLine($"Append Literal {s}");
builder.Append(s);
}
public void AppendFormatted<T>(T t)
{
if(!enabled) return;
Console.WriteLine($"Append Formatted {t}");
builder.Append(t.ToString());
}
internal string GetFormattedText() => builder.ToString();
}
public class Logger
{
public LogLevel EnabledLevel { get; init; } = LogLevel.Error;
public void LogMessage(LogLevel level, string msg)
{
Console.WriteLine("LogMessage string version");
if (EnabledLevel < level) return;
Console.WriteLine(msg);
}
public void LogMessage(LogLevel level, [InterpolatedStringHandlerArgument("", "level")]LogInterpolatedStringHandler builder)
{
Console.WriteLine("LogMessage interpolated string version");
if (EnabledLevel < level) return;
Console.WriteLine(builder.GetFormattedText());
}
}
var logger = new Logger();
logger.LogMessage(LogLevel.Critical, "Critical messsage");
Console.WriteLine("");
logger.LogMessage(LogLevel.Critical, $"Critical messsage {DateTime.Now}");
Console.WriteLine("");
logger.LogMessage(LogLevel.Information, $"Information messsage {DateTime.Now}");
//Output
/*
LogMessage string version
Critical messsage
Append Literal Critical messsage
Append Formatted 04/08/2022 09:47:35
LogMessage interpolated string version
Critical messsage 04/08/2022 09:47:35
LogMessage interpolated string version
*/
Example
References:
https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/interpolated-string-handler