16

这个问题类似,我想将可选参数与 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在采用较少参数的重载中显式指定,这不是所需的行为。

有什么方法可以做到这一点(也许使用一些我还没有学过的新属性?)还是我们只是达到了自动魔法编译器支持可以给我们的极限?

4

5 回答 5

17

我更喜欢的方式: 只有两个字符开销 - 虽然丑陋的语言'hack';

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

信息方法:

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);
于 2014-07-24T15:12:15.473 回答
8

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

首先,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类型传递。Astruct没有这个问题。

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

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

瞧!

这种类型的唯一目的是弄乱重载解析过程,但是如果您在方法采用我拥有的其他参数时不小心填写了调用者信息属性值(编译器应该提供),它也会导致编译器错误一些这样的调用立即导致编译器错误。

于 2014-11-06T16:46:12.793 回答
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;
        }
    }

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

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

语法对我来说似乎稍微干净一些(重复的 Log 仍然很难看,但确实如此),我认为使用上下文结构可能会使其性能稍好一些,尽管我必须进行分析才能确定。

于 2014-07-24T15:50:43.680 回答
2

方式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"));
}
于 2014-07-24T14:54:51.507 回答
2

如果您在“丑陋的解决方案”中使您的格式参数成为可选参数,则您不需要为每个数量的参数特别重载,但只有一个就足够了!例如:

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

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

或者您可以将其与 John Leidegren 解决方案结合使用,即在 argsX 和最后两个参数之间添加 guarging 参数....

于 2016-08-19T11:25:06.977 回答