可选参数以及参数数组
本文关键字:参数 数组 | 更新日期: 2023-09-27 17:56:05
我有一个日志记录接口,我用一些有用的扩展方法对其进行扩展,以便我可以传入格式和参数列表,以避免每次调用该方法时都必须使用字符串格式。(它还可以帮助我遵循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, 行: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)
这是不可能的,因为 [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]
,或者是否有其他方法可以获得预期的结果?
我遇到了同样的问题,但解决方式略有不同。 这不是最优雅的解决方案,但它有效并且相对干净:
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);
}
}
不能在方法签名中组合两者。您可以做的是一个或另一个,并将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;
我发现的最优雅的方法(或最不优雅的方式)是创建一个具有所需名称的方法,该方法提取属性信息并返回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
的情况下,结果是不可预测的。
将所有默认参数向右移动。