【问题标题】:Optional parameters together with an array of parameters可选参数以及参数数组
【发布时间】:2013-01-08 11:00:30
【问题描述】:

我有一个日志接口,我使用一些有用的扩展方法对其进行了扩展,以便我可以传递格式和参数列表,以避免每次调用该方法时都必须使用字符串格式。 (它也帮助我遵守 FXCops 文化信息规则)

所以我可以打电话:

logger.Debug("Created {0} with id {1}",typeof(MyObject).Name ,myObject.Id);

代替:

logger.Debug(string.Format("Created {0} with id {1}", typeof(MyObject).Name, myObject.Id));

我现在发现自己陷入了一个棘手的境地,因为在日志中获取一些关于日志写入位置的信息(例如文件、方法和行号)将非常有帮助。这可以通过简洁的[CallerMemberName][CallerFilePath][CallerLineNumber] 属性来实现。

logger.Debug("Created {0} with id {1}", typeof(MyObject).Name, myObject.Id);

然后会给我一个日志条目,例如:

“MyObjectProvider.cs, Provide, line:50 | Created MyObject with id 1564”

这里的问题是方法签名看起来像这样:

public static void Debug(this ILogger logger, string format [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0, params object[] args)

这是不可能的,因为 [Caller*] 属性使参数成为可选参数,而这不适用于 args 参数。

我还尝试使用固定数量的字符串作为参数进行多种实现,如下所示:

public static void Debug(this ILogger logger, string format [CallerMemberName] string callerMemberName = "",string arg, string arg2 , ...etc... , [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0)

然后我收到编译器错误,提示 “以下方法或属性之间的调用不明确”

我现在几乎已经放弃了这个问题,但我心想,“也许 SO 可以为我找到解决方案”。所以这里是... 是否可以同时使用params object[] args[CallerFilePath] 或者是否有其他方法可以获得预期的结果?

【问题讨论】:

标签: c# arguments optional-parameters ambiguous args


【解决方案1】:

将所有默认参数向右移动。

【讨论】:

  • 生成编译器错误:“无法为参数数组指定默认值”
  • 这个不行,至少把你的代码放到visual studio中看看能不能先编译
  • 将默认参数向右移动会使参数位于中间并且无法编译“参数数组必须是形参列表中的最后一个参数”
【解决方案2】:

您不能将两者结合在方法签名中。您可以做的只是其中之一并将null 传递到您需要可选参数的地方,这对您有用吗?

Foo(s, null);
public void Foo(string s, params string[] sArray)
{

}

Foo(new string[] {""});
private static void Foo(string[] sArray,  string s = "")
{
}

为什么不使用一个类来处理您的格式并将其设为可选?

public class LogArgs
{
  private string _formatString;
  private string[] _args;
  public LogArgs(string formatString, params string[] args)
  {
    _formatString = formatString;
    _args = args;
  }
  public override string ToString()
  {
    return string.Format(_formatString, _args);
  }
}

public void Foo(string mandatory, LogArgs optionalParam = null)
{
  //Do Stuff
}

Foo("", new LogArgs("{0} is formatted", ""));

【讨论】:

  • @Animal 我的意思是你可以保持你的方法不变,但你只需要牺牲使用可选参数或参数。就个人而言,我会用 object[] args 替换 params object[] args,然后以这种方式传入数组 - 你明白我的意思吗?
  • 这可行,但对该方法的调用看起来很混乱,而且使用起来也不是很简单,因为您必须为 string.format 创建参数数组。如果这是唯一的解决方案,那么在记录之前格式化消息可能会更好。
  • @Animal 我明白了……你觉得我的编辑怎么样?这可能有帮助吗?
  • 嗯...这是Foo("", new LogArgs("{0} is formatted", ""));Foo("", string.Format("{0} is formatted", "")); 好吗?我认为它基本上是相同的,编码人员知道什么是 string.Format 所以也许这是一个更好的可读性解决方案
  • @Animal 我同意,可能不是。唯一的好处是,如果你想扩展 LogArgs 类来做一些 string.Format 可能做不到的其他事情。
【解决方案3】:

我找到了另一种使用 StackTrace 获取所需信息的方法。它在优化代码中有点不安全,而且速度很慢,但出于调试目的,只要可以在发布版本中关闭它,它就可以很好地工作。

StackTrace stackTrace = new StackTrace();
var callerMember = stackTrace.GetFrame(1).GetMethod();
var callerMemberName = callerMember.Name;
var callerType = callerMember.ReflectedType.Name;

【讨论】:

    【解决方案4】:

    我遇到了同样的问题,但解决方法有所不同。这不是最优雅的解决方案,但它可以工作并且相对干净:

    public class SrcLoc
    {
        public string sourceFile { get; set; }
        public int lineNumber { get; set; }
        public SrcLoc([CallerFilePath] string sourceFile = "",
                      [CallerLineNumber] int lineNumber = 0)
        {
          this.sourceFile = sourceFile;
          this.lineNumber = lineNumber;
        }
    }
    public class Logger
    {
       public void Log(SrcLoc location,
                    int level = 1,
                    string formatString = "",
                    params object[] parameters)
      {
         string message = String.Format(formatString, parameters);
      }
    }
    public MainTest
    {
        public static void Main()
        {
            string file="filename";
            logger.Log(new SrcLoc(), (int)LogLevel.Debug, "My File: {0}", file);
        }
    }
    

    【讨论】:

      【解决方案5】:

      我发现的最优雅的方式(或最不优雅的方式)是创建一个提取属性信息并返回Action 委托的所需名称的方法。然后,您使用您实际要调用的签名设置此委托。

      所以,从

      public static void Debug(this ILogger logger, string format, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0, params object[] args)
      

      创建一个委托

      public delegate void LogDelegate(string format, params object[] args);
      

      从您的方法调用返回:

      public static void Debug(this ILogger logger, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0)
      {
        return (format, args)
        {
          LogWithCallerSiteInfo(format, args, callerMemberName, callerFilePath, callerLineNumber, logAction);
        }
      }
      

      并使用捕获的数据调用辅助方法:

      private static void LogWithCallerSiteInfo(string format, object[] args, string callerMemberName, string callerFilePath, int callerLineNumber, Action<string, object[]> logRequest)
          {
              if (args == null)
              {
                  args = new object[0];
              }
              var args2 = new object[args.Length + 3];
              args.CopyTo(args2, 0);
              args2[args.Length] = sourceFile;
              args2[args.Length + 1] = memberName;
              args2[args.Length + 2] = lineNumber;
      
              logRequest(format + " [{callerFilePath:l}.{callerMemberName:l}-{callerLineNumber}]", args2);
          }
      

      然后拨打电话,因此:

      logger.Debug()("Created {0} with id {1}",typeof(MyObject).Name ,myObject.Id);
      

      因此,就用法而言,您插入了一组额外的(),它捕获了呼叫站点信息,并且该集合集合在委托上进行呼叫。这和我设法做到的一样整洁。

      我已经重新创建了params 数组,添加了捕获的数据,否则(至少使用SeriLog,结果是不可预测的。

      【讨论】:

        猜你喜欢
        • 2011-02-08
        • 2014-08-21
        • 2021-01-12
        • 2017-02-19
        • 1970-01-01
        • 2019-11-17
        • 2012-03-14
        • 2011-09-20
        • 1970-01-01
        相关资源
        最近更新 更多