Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use params keyword along with caller Information in C#?

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.

like image 244
Moby Disk Avatar asked Nov 03 '14 16:11

Moby Disk


People also ask

What is the use of params keyword?

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.

What is params modifier?

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.

What is params type in C#?

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.

Which statement is true about params used as method parameter in C#?

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.


3 Answers

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.

  1. 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);
    
  2. 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:

    1. Your log method can catch exceptions produced while producing the debug string, so instead of breaking your system you just get an error that says: "Failed to produce log message: [exception details]".
    2. 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));
    
like image 164
StriplingWarrior Avatar answered Sep 24 '22 21:09

StriplingWarrior


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

like image 44
CharlesNRice Avatar answered Sep 24 '22 21:09

CharlesNRice


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;

like image 22
Erti-Chris Eelmaa Avatar answered Sep 24 '22 21:09

Erti-Chris Eelmaa