To build on @guilhermekmelo's answer, I might suggest using a chained method:
So keep your current Log(string,string,object[]
method:
public void Log(string methodName, string messageFormat, params object[] messageParameters)
And add this new overload (Log(string,string)
):
public LogMessageBuilder Log(string messageFormat, [CallerMemberName] string methodName = null)
{
// Where `this.Log` is
return new LogMessageBuilder( format: messageFormat, logAction: this.Log );
}
public struct LogMessageBuilder
{
private readonly String format;
private readonly String callerName;
private readonly Action<String,String,Object[]> logAction;
public LogMessageBuilder( String format, String callerName, Action<String,String,Object[]> logAction )
{
this.format = format;
this.callerName = callerName;
this.logAction = logAction;
}
public void Values( params Object[] values )
{
this.logAction( this.format, this.callerName, values );
}
}
Used like so:
this.Log( "My formatted message a1 = {0}, a2 = {2}" ).Values( 10, "Nice" );
Note that LogMessageBuilder
is a struct
, so it's a value-type, which means it won't cause another GC allocation - though the use of params Object[]
will cause an array allocation at the call-site. (I wish C# and .NET supported stack-based variadic parameters instead of faking it with a heap-allocated parameter array).
Another option is to use FormattableString
- but note that due to how the C# compiler has built-in special-case magic for FormattableString
you need to be careful not to let it be implicitly converted to String
(also it sucks that you can't add extension-methods to FormattableString
directly, grumble):
public void Log(FormattableString fs, [CallerMemberName] string methodName = null)
{
this.Log( messageFormat: fs.Format, methodName: methodName, messageParameters: fs.GetArguments() );
}
Usage:
this.Log( $"My formatted message a1 = {10}, a2 = {"Nice"}" );