Search code examples
c#.netparametersoptional-parameterscallermembername

Mixing optional parameters and params when can't simply overload


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?


Solution

  • My prefered way: Only two characters overhead (ugly language 'hack' though)

    public delegate void WriteDelegate(string message, params object[] args);
    
    public static WriteDelegate Info(
          [CallerMemberName] string memberName = "", 
          [CallerLineNumber] int lineNumber = 0)
     {
         return new WriteDelegate ((message,args)=>
         {
             _log.Info(BuildMessage(message, memberName , lineNumber ), args);
         });
     }
    

    Usage (supply your own implementation of BuildMessage

    Info()("hello world {0} {1} {2}",1,2,3);
    

    Alternative

    The way my collegue came up to make this work was like this:

    public static class DebugHelper
        
        public static Tuple<string,int> GetCallerInfo(
          [CallerMemberName] string memberName = "", 
          [CallerLineNumber] int lineNumber = 0)
        {
            return Tuple.Create(memberName,lineNumber);
        }
    }
    

    The InfoMethod:

    public void Info(Tuple<string,int> info, string message, params object[] args)
    {
          _log.Info(BuildMessage(message, info.Item1, info.Item2), args);
    }
    

    usage:

      instance.Info(DebugHelper.GetCallerInfo(),"This is some test {0} {1} {2}",1,2,3);