C# 可选参数以及参数数组

C# 可选参数以及参数数组,c#,arguments,optional-parameters,ambiguous,args,C#,Arguments,Optional Parameters,Ambiguous,Args,我有一个日志接口,我用一些有用的扩展方法扩展了它,使我能够传递一种格式和一系列参数,以避免每次调用该方法时都必须使用字符串格式。(这也有助于我遵守FXCops文化信息规则) 所以我可以打电话: logger.Debug("Created {0} with id {1}",typeof(MyObject).Name ,myObject.Id); 而不是: logger.Debug(string.Format("Created {0} with id {1}", typeof(MyObject).

我有一个日志接口,我用一些有用的扩展方法扩展了它,使我能够传递一种格式和一系列参数,以避免每次调用该方法时都必须使用字符串格式。(这也有助于我遵守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,提供,行:50 |已创建id为1564的MyObject

这里的问题是,方法签名如下所示:

public static void Debug(this ILogger logger, string format [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0, params object[] 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)
这是不可能的,因为
[Caller*]
属性使参数成为可选的,而这不适用于args参数

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

public static void Debug(this ILogger logger, string format [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0, params object[] 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)
但是我得到了编译器错误,说“调用在以下方法或属性之间不明确”


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

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

无法在方法签名中组合这两个参数。您可以做的是一个或另一个,并将
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", ""));

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

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

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

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);
    }
}
我发现的最优雅的方法(或最不优雅的方法)是使用所需名称创建一个方法,该方法提取属性信息并返回一个
操作
委托。然后,使用实际要调用的签名设置此委托

那么,从

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);
    }
因此,在用法方面,您可以插入一组额外的
()
,它捕获呼叫站点信息,并由该集合对代理进行呼叫。这是我能做到的最整洁的了


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

生成编译器错误:“无法为参数数组指定默认值”你不能这样做,至少把你的代码放到VisualStudio中,看看它是否编译第一次移动默认的参数到右边,使PARAM处于中间,而不编译“参数数组必须是正式参数列表中的最后一个参数”。@我想说的是,你可以保持你的方法不变,但你只需要牺牲使用可选参数或参数。就个人而言,我会用
object[]args
替换
object[]args
然后以这种方式传入数组-你明白我的意思吗?这会起作用,但是对该方法的调用看起来非常混乱,而且使用起来也不是很简单,因为你必须创建string.format的参数数组。如果这是唯一的解决方案,那么最好在l之前格式化消息我看到了。@Animal…你觉得我的编辑怎么样?这可能有帮助吗?嗯…这是
Foo(“,新的LogArgs(“{0}已格式化”,”);
Foo(“,string.Format(“{0}已格式化”,”)好吗;
?我认为基本上是一样的,编码人员知道什么是string.Format,所以这可能是一个更好的解决方案readability@Animal我同意,它可能不是。唯一的好处是如果您想扩展
LogArgs
类来做一些
string.Format
可能看不到的其他事情