Similar to this question, I want to mix optional parameters with the params keyword, which of course creates ambiguity. Unfortunately, the answer of creating overloads does not work, as I want to take advantage of caller info attributes, like this:
public void Info(string message, [CallerMemberName] string memberName = "",
[CallerLineNumber] int lineNumber = 0, params object[] args)
{
_log.Info(BuildMessage(message, memberName, lineNumber), args);
}
Creating an overload without the optional parameters would change the call-site, preventing these particular parameters from working properly.
I found a solution that almost works (though it's ugly):
public void Info(string message, object arg0, [CallerMemberName] string memberName = "",
[CallerLineNumber] int lineNumber = 0)
{
_log.Info(BuildMessage(message, memberName, lineNumber), arg0);
}
public void Info(string message, object arg0, object arg1, [CallerMemberName] string memberName = "",
[CallerLineNumber] int lineNumber = 0)
{
_log.Info(BuildMessage(message, memberName, lineNumber), arg0, arg1);
}
The problem here is that if you specify a string for the last argument, the overload resolution assumes you're intending to explicitly specify memberName
in the overload that takes fewer arguments, which is not the desired behavior.
Is there some way to accomplish this (perhaps using some new attributes I haven't learned about?) or have we simply reached the limits of what the auto-magical compiler support can give us?
If you make your format parameters optional in your "Ugly solution" you do not need speacial overload for each number of parameters but only one is enough for all! e.g:
then you can call it with up to three parameters i.e.
You can easily minimize the risk of accidentally filling CallerMemberName and CallerLineNumber by adding much more optional formating arguments than you will ever need e.g. arg0, ... arg20.
or you can combine it with John Leidegren solution i.e adding guarging parameter.... between argsX and last two params...
Based on the answers others provided, I can see that they are largely based on capturing the context first, then invoking the logging method with the captured context. I came up with this:
If you have a LoggerProxy (class defining method
Info()
) named Log, the usage is like this:The syntax seems slightly cleaner to me (duplicate Log is still ugly, but so it goes) and I think using a struct for the context may make it slightly better as far as performance, though I'd have to profile to be sure.
So, I actually ran into this problem but for a different reason. Eventually I solved it like this.
First, overload resolution in C# (generic methods are ideal candidates). I used T4 to generate these extension method overloads with support for up to 9 arguments. Here is an example with just 3 arguments.
Which works fine for a while but eventually results in an ambiguity when you use any combination of arguments that match the caller info attribute list. To prevent this from happening you need a type to guard the optional parameter list and separate it from the optional parameter list.
An empty struct will do just fine (I use long and descriptive names for such things).
Insert this type between the actual parameter list and the optional parameter list.
Voilà!
The only purpose this type has is to mess with the overload resolution procedure but it will also result in a compiler error if you accidently fill-in the caller info attribute values (that the compiler should have provided) when your methods take additional parameters I had some such calls that resulted in compiler errors right away.
Way 1.
I You can use
StackFrame
instead ofCallerLineNumber
:Useful documentation pages:
Way 2.
My prefered way: Only two charachters overhead - ugly language 'hack' though;
Usage (supply your own implementation of
BuildMessage
Alternative
The way my collegue came up to make this work was like this:
The InfoMethod:
usage: