在无法简单过载的情况下混合可选参数和参数

时间:2014-07-24 14:31:50

标签: c# .net params optional-parameters callermembername

this question类似,我想将可选参数与params关键字混合,这当然会产生歧义。不幸的是,创建重载的答案不起作用,因为我想利用调用者信息属性,如下所示:

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

在没有可选参数的情况下创建重载会更改调用站点,从而阻止这些特定参数正常工作。

我找到了一个几乎可行的解决方案(尽管很难看):

    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);
    }

这里的问题是,如果为最后一个参数指定一个字符串,则重载决策假定您打算在重载中显式指定memberName,该参数占用较少的参数,这不是所需的行为。 / p>

是否有某种方法可以实现这一点(可能使用了一些我尚未了解的新属性?)或者我们是否只是达到了自动魔法编译器支持可以为我们提供的限制?

5 个答案:

答案 0 :(得分:17)

我的首选方式: 只有两个开销人员 - 丑陋的语言和黑客攻击'虽然;

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);
     });
 }

用法(提供您自己的BuildMessage

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

<强>替代

我的同事提出这项工作的方式是这样的:

public static class DebugHelper

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

InfoMethod:

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

用法:

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

答案 1 :(得分:7)

所以,我实际上遇到了这个问题,但原因不同。最终我像这样解决了它。

首先,C#中的重载决策(通用方法是理想的候选者)。我使用T4生成这些扩展方法重载,最多支持9个参数。这是一个只有3个参数的例子。

public static void WriteFormat<T1, T2, T3>(this ILogTag tag, string format, T1 arg0, T2 arg1, T3 arg2
    , [CallerMemberName] string callerMemberName = null, [CallerFilePath] string callerFilePath = null, [CallerLineNumber] int callerLineNumber = 0
    )
{
    if (tag != null)
    {
        var entry = new LogEntry(DateTimeOffset.Now, tag.TagName, new LogString(format, new object[] { arg0, arg1, arg2 }), callerMemberName, System.IO.Path.GetFileName(callerFilePath), callerLineNumber);
        tag.Write(entry);
    }
}

一段时间内可以正常工作,但是当您使用与调用者信息属性列表匹配的任何参数组合时,最终会导致歧义。为了防止这种情况发生,您需要一个类型来保护可选参数列表并将其与可选参数列表分开。

一个空结构就可以了(我为这些东西使用了很长的描述性名称)。

/// <summary>
/// The purpose of this type is to act as a guard between 
/// the actual parameter list and optional parameter list.
/// If you need to pass this type as an argument you are using
/// the wrong overload.
/// </summary>
public struct LogWithOptionalParameterList
{
    // This type has no other purpose.
}
  

注意:我想过将这个作为一个带有私有构造函数的抽象类,但这实际上允许null作为LogWithOptionalParameterList类型传递。 struct没有此问题。

在实际参数列表和可选参数列表之间插入此类型。

public static void WriteFormat<T1, T2, T3>(this ILogTag tag, string format, T1 arg0, T2 arg1, T3 arg2
    , LogWithOptionalParameterList _ = default(LogWithOptionalParameterList)
    , [CallerMemberName] string callerMemberName = null, [CallerFilePath] string callerFilePath = null, [CallerLineNumber] int callerLineNumber = 0
    )
{
    if (tag != null)
    {
        var entry = new LogEntry(DateTimeOffset.Now, tag.TagName, new LogString(format, new object[] { arg0, arg1, arg2 }), callerMemberName, System.IO.Path.GetFileName(callerFilePath), callerLineNumber);
        tag.Write(entry);
    }
}

瞧!

此类型的唯一目的是搞乱重载解析过程,但如果您的方法需要额外填写调用者信息属性值(编译器应该提供),它也会导致编译器错误参数我有一些这样的调用,立即导致编译器错误。

答案 2 :(得分:3)

根据其他人提供的答案,我可以看到它们主要基于首先捕获上下文,然后使用捕获的上下文调用日志记录方法。我想出了这个:

    public CallerContext Info([CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0)
    {
        return new CallerContext(_log, LogLevel.Info, memberName, lineNumber);
    }

    public struct CallerContext
    {
        private readonly Logger _logger;
        private readonly LogLevel _level;
        private readonly string _memberName;
        private readonly int _lineNumber;

        public CallerContext(Logger logger, LogLevel level, string memberName, int lineNumber)
        {
            _logger = logger;
            _level = level;
            _memberName = memberName;
            _lineNumber = lineNumber;
        }

        public void Log(string message, params object[] args)
        {
            _logger.Log(_level, BuildMessage(message, _memberName, _lineNumber), args);
        }

        private static string BuildMessage(string message, string memberName, int lineNumber)
        {
            return memberName + ":" + lineNumber + "|" + message;
        }
    }

如果你有一个名为Log的LoggerProxy(类定义方法Info()),则用法如下:

Log.Info().Log("My Message: {0}", arg);

语法似乎对我来说稍微清晰一点(重复的Log仍然很难看,但事实如此)我认为在上下文中使用结构可能会使性能稍微好一点,尽管我不得不将其描述为肯定的。

答案 3 :(得分:2)

如果您在&#34;丑陋的解决方案中使您的格式参数可选&#34;你不需要为每个参数提供特殊的重载,但只有一个对所有参数都足够了! e.g:

public void Info(string message, object arg0=null, object arg1=null,
[CallerMemberName] string memberName = "",[CallerLineNumber] int lineNumber = 0)
{
    _log.Info(BuildMessage(message, memberName, lineNumber), arg0, arg1);
}

然后你可以用最多三个参数调用它,即

Info("No params");
Info("One param{0}",1);
Info("Two param {0}-{1}",1,2);

您可以通过添加比您需要的更多可选格式化参数,轻松地将意外填充CallerMemberName和CallerLineNumber的风险降至最低。 arg0,... arg20。

或者你可以将它与John Leidegren解决方案结合起来,即在argsX和最后两个参数之间添加guarging参数....

答案 4 :(得分:1)

方式1。

我可以使用StackFrame代替CallerLineNumber

public void Info(string message, params object[] args)
{
  StackFrame callStack = new StackFrame(1, true);
  string memberName = callStack.GetMethod().Name;
  int lineNumber = callStack.GetFileLineNumber();
  _log.Info(BuildMessage(message, memberName, lineNumber), args);
}

有用的文档页面:

方式2。

public class InfoMessage
{
  public string Message { get; private set; }
  public string MemberName { get; private set; }
  public int LineNumber { get; private set; }

  public InfoMessage(string message,
                     [CallerMemberName] string memberName = "", 
                     [CallerLineNumber] int lineNumber = 0)
  {
    Message = message;
    MemberName = memberName;
    LineNumber = lineNumber;
  }
}

public void Info(InfoMessage infoMessage, params object[] args)
{ 
  _log.Info(BuildMessage(infoMessage), args);
}

public string BuildMessage(InfoMessage infoMessage)
{
  return BuildMessage(infoMessage.Message, 
    infoMessage.MemberName, infoMessage.LineNumber);
}

void Main()
{
  Info(new InfoMessage("Hello"));
}