简体   繁体   English

可选参数以及一系列参数

[英]Optional parameters together with an array of parameters

I have a logging interface which I extend with some helpful extension methods as to make it possible for me to pass in a format and a list of arguments to avoid having to use string format every time a call the method. 我有一个日志记录界面,我扩展了一些有用的扩展方法,使我可以传入一个格式和一个参数列表,以避免每次调用方法时都必须使用字符串格式。 (it also help me follow FXCops culture info rules) (它也帮助我遵循FXCops文化信息规则)

So I can call: 所以我可以打电话:

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

Instead of: 代替:

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

I now found myself in a bit of a tricky situation because it would be immensely helpful to also get some info in the logs about where the logging was written such as the file, method, and line number. 我现在发现自己处于一个棘手的状态,因为在日志中获取有关日志写入位置的一些信息(例如文件,方法和行号)会非常有帮助。 This could be achieved with the neat [CallerMemberName] , [CallerFilePath] , and [CallerLineNumber] attribute. 这可以通过整洁的[CallerMemberName][CallerFilePath][CallerLineNumber]属性来实现。

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

would then give me a log entry such as: 然后会给我一个日志条目,例如:

"MyObjectProvider.cs, Provide, line:50 | Created MyObject with id 1564" “MyObjectProvider.cs,Provide,line:50 |创建了ID为1564的MyObject”

The issue here is that the method signature would look like this: 这里的问题是方法签名看起来像这样:

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

and that is not possible because the [Caller*] attributes makes the parameters optional and that doesn't work with the args parameter. 这是不可能的,因为[Caller*]属性使参数成为可选参数,并且不适用于args参数。

I also tried to make multiple implementations with fixed amount of strings as parameters like this: 我还尝试使用固定数量的字符串作为参数进行多次实现,如下所示:

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

but then I get compiler errors saying the "The call is ambiguous between the following methods or properties" 但后来我得到编译器错误,说“以下方法或属性之间的调用是模糊的”

I have almost given up on this issue now but I thought to myself, "Maybe SO can find a solution for me". 我现在几乎放弃了这个问题,但我心想,“也许我可以为我找到一个解决方案”。 So here it is... Is it possible to use both params object[] args and [CallerFilePath] in any way or is there another way to get the intended result? 所以这里是...... 是否有可能以任何方式使用params object[] args[CallerFilePath] ,还是有另一种方法来获得预期的结果?

You can't combine the two in a method signature. 您不能在方法签名中组合这两者。 What you could do is one or the other and pass in null to where you need optional parameters, would this work for you? 你可以做的是一个或另一个并传入null到你需要可选参数的地方,这对你有用吗?

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

}

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

OR 要么

Why not use a class which handles your formatting and make that optional? 为什么不使用一个处理格式化的类并使其可选?

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

I found another way to get the information I want by using StackTrace. 我找到了另一种使用StackTrace获取所需信息的方法。 It's a bit unsafe in optimized code and it's very slow but for debuging purposes it works great as long as it's possible to shut it off in release builds. 它在优化的代码中有点不安全,而且速度非常慢但是为了进行调试,只要可以在发布版本中将其关闭,它就能很好地工作。

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

I ran into the same issue, but solved it a little differently. 我遇到了同样的问题,但解决方法有点不同。 It's not the most elegant solution, but it works and it's relatively clean: 这不是最优雅的解决方案,但它的工作原理相对干净:

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

The most elegant way (or least inelegant way) I've found is to create a method with the required name that extracts the attribute information and returns an Action delegate. 我发现最优雅的方式(或者最不优雅的方式)是创建一个具有所需名称的方法,该方法提取属性信息并返回一个Action委托。 You then setup this delegate with the signature that you actually want to call. 然后,使用您实际要调用的签名设置此委托。

So, from 所以,来自

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

create a delegate 创建一个委托

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

which is returned from your method call: 从您的方法调用返回:

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

and calls a helper method with the captured data: 并使用捕获的数据调用辅助方法:

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

And make the call, thus: 然后拨打电话:

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

So, in usage terms, you insert an extra set of () , which captures the call-site info and the set set makes the call on the delegate. 因此,在使用术语中,您插入一组额外的() ,它捕获呼叫站点信息,而集合组则在委托上进行调用。 That's as neat as I've managed to make it. 这就像我设法做到的那样整洁。

I've recreated the params array adding in the captured data, otherwise, (at least with SeriLog , the results are unpredictable. 我重新创建了捕获数据中的params数组,否则,(至少在SeriLog ,结果是不可预测的。

将所有默认参数移动到右侧。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM