当不能简单地过载时,混合可选参数和参数
与此问题类似,我想将可选参数与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
假定您打算在重载中显式指定memberName
,该参数占用较少的参数,这不是所需的行为。
有没有办法实现这一点(也许使用一些我没有学过的新属性?)或者我们是否只是达到了自动魔法编译器支持可以给我们的极限?
我最喜欢的方式:开头只有两个字符 – 丑陋的语言’黑客’;
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 GetCallerInfo( [CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0) { return Tuple.Create(memberName,lineNumber); } }
InfoMethod:
public void Info(Tuple 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);
所以,我实际上遇到了这个问题,但原因不同。 最终我像这样解决了它。
首先,C#中的重载决策(通用方法是理想的候选者)。 我使用T4生成这些扩展方法重载,最多支持9个参数。 这是一个只有3个参数的例子。
public static void WriteFormat(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); } }
哪个工作正常一段时间但最终导致使用与调用者信息属性列表匹配的任何参数组合时的歧义。 为了防止这种情况发生,您需要一个类型来保护可选参数列表并将其与可选参数列表分开。
一个空结构就可以了(我为这些东西使用长而具有描述性的名称)。
/// /// 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. /// public struct LogWithOptionalParameterList { // This type has no other purpose. }
注意:我考虑使用私有构造函数使其成为一个抽象类,但实际上允许将
null
作为LogWithOptionalParameterList
类型传递。struct
没有这个问题。
在实际参数列表和可选参数列表之间插入此类型。
public static void WriteFormat(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); } }
瞧!
这种类型的唯一目的是搞乱重载解析过程,但是当你的方法采用我有的其他参数时,如果你意外填写调用者信息属性值(编译器应该提供),它也会导致编译器错误一些此类调用立即导致编译器错误。
基于其他人提供的答案,我可以看到它们主要基于首先捕获上下文,然后使用捕获的上下文调用日志记录方法。 我想出了这个:
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; } }
如果你有一个名为Log的LoggerProxy(类定义方法Info()
),用法如下:
Log.Info().Log("My Message: {0}", arg);
语法似乎对我来说稍微清晰一点(重复的Log仍然很难看,但事实如此)并且我认为在上下文中使用结构可能会使性能稍微好一点,尽管我必须要进行分析才能确定。
如果你在“丑陋的解决方案”中使你的格式参数成为可选的,你不需要为每个参数提供特殊的重载,但只有一个对所有参数都足够了! 例如:
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参数….
方式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); }
有用的文档页面:
- StackFrame类
- StackFrame.GetFileLineNumber方法
- StackFrame.GetMethod方法
方式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")); }