I am trying to combine the C# 5.0 Caller Information along with the C# params keyword. The intention is to create a wrapper for a logging framework, and we want the logger to format the text like String.Format. In previous versions, the method looked like this:
void Log(
string message,
params object[] messageArgs = null);
And we call it like this:
log.Log("{0}: I canna do it cap'n, the engines can't handle warp {1}!",
"Scotty", warpFactor);
Now, we want to capture caller information and log that as well. So the signature becomes:
void Log(
string message,
params object[] messageArgs,
[CallerMemberName] string sourceMemberName = null);
That doesn't compile because the params must be last parameter. So I try this:
void Log(
string message,
[CallerMemberName] string sourceMemberName = null,
params object[] messageArgs);
Is there a way to call that without either providing sourceMembername, or assigning the messageArgs argument explicitly as a named parameter? Doing so defeats the purpose of the params keyword:
// params is defeated:
log.Log("message",
messageArgs: new object[] { "Scotty", warpFactor });
// CallerMemberName is defeated:
log.Log("message", null,
"Scotty", warpFactor);
Is there a way to do this? It seems like the "hacky" way the caller information is passed precludes using the params keyword. It would be awesome if the C# compiler recognized that the caller member information parameters aren't really parameters at all. I can see no need to ever pass them explicitly.
My backup will be to skip the params keyword, and the caller will have to use the long signature in that last example.
By using the params keyword, you can specify a method parameter that takes a variable number of arguments. The parameter type must be a single-dimensional array. No additional parameters are permitted after the params keyword in a method declaration, and only one params keyword is permitted in a method declaration.
In C#, params is a keyword which is used to specify a parameter that takes variable number of arguments. It is useful when we don't know the number of arguments prior. Only one params keyword is allowed and no additional parameter is permitted after params keyword in a function declaration.
Params is an important keyword in C#. It is used as a parameter which can take the variable number of arguments. Important Point About Params Keyword : It is useful when programmer don't have any prior knowledge about the number of parameters to be used.
The "params" keyword in C# allows a method to accept a variable number of arguments. C# params works as an array of objects. By using params keyword in a method argument definition, we can pass a number of arguments. Note: There can't be anymore parameters after a params.
I don't think that it can be done in exactly the way you want to do it. However, I could think of a few viable workarounds which would probably give you almost the same benefits.
Use an intermediate method call to capture the caller member name. The first method call returns a delegate which can in turn be called to provide the additional parameters. This looks weird, but it should work:
log.Log()("{0}: I canna do it cap'n, the engines can't handle warp {1}!",
"Scotty", warpFactor);
One disadvantage here is that it's possible to call log.Log("something")
, expecting that your message will be logged, and nothing will happen. If you use Resharper, you can mitigate this by adding a [Pure]
attribute to the Log()
method so you get a warning if someone doesn't do anything with the resulting object. You could also tweak this approach slightly, saying:
var log = logFactory.GetLog(); // <--injects method name.
log("{0}: I canna do it cap'n, the engines can't handle warp {1}!",
"Scotty", warpFactor);
Produce your log messages with lambdas, and let string.Format take care of the params array:
log.Log(() => string.Format("{0}: I canna do it cap'n, the engines can't handle warp {1}!",
"Scotty", warpFactor));
This is the approach that I typically use, and it has some side advantages:
Sometimes the object you pass to your format string might incur additional cost, which you'd only want to incur when you need it:
log.Info(() => string.Format("{0}: I canna do it cap'n, the engines can't handle warp {1}!",
_db.GetCurrentUsername(), warpFactor));
You'd prefer not to have the above code do a database trip if info-level logging is not turned on.
As a side note, I find myself using string.Format often enough that I've created a helper method to shorten the syntax slightly:
log.Log(() => "{0}: I canna do it cap'n, the engines can't handle warp {1}!"
.With("Scotty", warpFactor));
To go along with StriplingWarrior suggestion instead of a delegate you could do it with a fluent syntax.
public static class Logger
{
public static LogFluent Log([CallerMemberName] string sourceMemberName = null)
{
return new LogFluent(sourceMemberName);
}
}
public class LogFluent
{
private string _callerMemeberName;
public LogFluent(string callerMamberName)
{
_callerMemeberName = callerMamberName;
}
public void Message(string message, params object[] messageArgs)
{
}
}
Then call it like
Logger.Log().Message("{0}: I canna do it cap'n, the engines can't handle warp {1}!", "Scotty", 10);
Logger doesn't have to be static but it was a simple way to demonstrate the concept
Well, let me mention one option; you can use reflection to get exact same name, as CallMemberName
. This will be definitely slower. Assuming you don't log every millisecond, it will be enough to handle the pressure, I believe.
var stackTrace = new StackTrace();
var methodName = stackTrace.GetFrame(1).GetMethod().Name;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With