如何在C#中使用params关键字和调用者信息?

时间:2014-11-03 16:51:43

标签: c# params-keyword callermembername

我正在尝试将C#5.0来电者信息与C#params关键字结合起来。目的是为日志框架创建一个包装器,我们希望记录器像String.Format一样格式化文本。在以前的版本中,该方法如下所示:

void Log(
   string message,
   params object[] messageArgs = null);

我们称之为:

log.Log("{0}: I canna do it cap'n, the engines can't handle warp {1}!", 
        "Scotty", warpFactor);

现在,我们想要捕获来电者信息并记录下来。签名变为:

void Log(
    string message,
    params object[] messageArgs,
    [CallerMemberName] string sourceMemberName = null);

这不会编译,因为params必须是最后一个参数。所以我试试这个:

void Log(
    string message,
    [CallerMemberName] string sourceMemberName = null,
    params object[] messageArgs);

有没有办法在没有提供sourceMembername的情况下调用它,或者将messageArgs参数显式指定为命名参数?这样做会破坏params关键字的目的:

// params is defeated:
log.Log("message", 
        messageArgs: new object[] { "Scotty", warpFactor });
// CallerMemberName is defeated:
log.Log("message", null,
        "Scotty", warpFactor);

有办法做到这一点吗?这似乎是" hacky"传递调用者信息的方式排除了使用params关键字。如果C#编译器认识到调用者成员信息参数根本不是真正的参数,那将是非常棒的。我认为没有必要明确地传递它们。

我的备份将跳过params关键字,并且调用者必须在最后一个示例中使用长签名。

4 个答案:

答案 0 :(得分:7)

我不认为它可以按照您想要的方式完成。但是,我可以想到一些可行的解决方法,可能会给你几乎相同的好处。

  1. 使用中间方法调用来捕获调用者成员名称。第一个方法调用返回一个委托,该委托又可以被调用以提供附加参数。这看起来很奇怪,但应该有效:

    log.Log()("{0}: I canna do it cap'n, the engines can't handle warp {1}!", 
    "Scotty", warpFactor);
    

    这里的一个缺点是,可以调用log.Log("something"),期望您的消息将被记录,并且不会发生任何事情。如果您使用Resharper,则可以通过向[Pure]方法添加Log()属性来缓解此问题,以便在有人对结果对象执行任何操作时收到警告。您也可以稍微调整一下这种方法,并说:

    var log = logFactory.GetLog(); // <--injects method name.
    log("{0}: I canna do it cap'n, the engines can't handle warp {1}!", 
        "Scotty", warpFactor);
    
  2. 使用lambdas生成日志消息,并让string.Format处理params数组:

    log.Log(() => string.Format("{0}: I canna do it cap'n, the engines can't handle warp {1}!", 
        "Scotty", warpFactor));
    

    这是我通常使用的方法,它有一些优势:

    1. 您的日志方法可以捕获生成调试字符串时产生的异常,因此您不会破坏系统,而是会收到错误消息:&#34;无法生成日志消息:[异常详细信息]&#34;。< / LI>
    2. 有时,您传递给格式字符串的对象可能会产生额外费用,而您只需要在需要时支付费用:

      log.Info(() => string.Format("{0}: I canna do it cap'n, the engines can't handle warp {1}!", 
          _db.GetCurrentUsername(), warpFactor));
      

      如果未启用信息级日志记录,您不希望上述代码执行数据库访问。

    3. 作为旁注,我发现自己经常使用string.Format,我已经创建了一个辅助方法来稍微缩短语法:

      log.Log(() => "{0}: I canna do it cap'n, the engines can't handle warp {1}!" 
          .With("Scotty", warpFactor));
      

答案 1 :(得分:4)

要使用StriplingWarrior建议而不是委托,您可以使用流利的语法来完成。

public static class Logger
{
    public static LogFluent Log([CallerMemberName] string sourceMemberName = null)
    {
        return new LogFluent(sourceMemberName);
    }
}

public class LogFluent
{
    private string _callerMemeberName;

    public LogFluent(string callerMamberName)
    {
        _callerMemeberName = callerMamberName;
    }

    public void Message(string message, params object[] messageArgs)
    {

    }
}

然后将其称为

Logger.Log().Message("{0}: I canna do it cap'n, the engines can't handle warp {1}!", "Scotty", 10);

记录器不一定是静态的,但它是演示概念的简单方法

答案 2 :(得分:1)

好吧,让我提一个选项;您可以使用反射来获得与CallMemberName完全相同的名称。这肯定会慢一些。我相信,假设你没有记录每一毫秒,那就足以应付压力了。

var stackTrace = new StackTrace(); var methodName = stackTrace.GetFrame(1).GetMethod().Name;

答案 3 :(得分:1)

我喜欢跟随Jim Christophers(@beefarino)的复数课程来设置我自己的伐木项目。为此,我将ILog接口克隆为ILogger,我实现了 - 比如在一个名为LoggerAdapter的类中 - 然后我使用Jim Christophers LogManager来获取GetLogger(类型类型)-Method,它返回一个包装的log4net-logger&#39; LoggerAdapter&#39;:

namespace CommonLogging
{
    public class LogManager : ILogManager
    {
        private static readonly ILogManager _logManager;

        static LogManager()
        {
            log4net.Config.XmlConfigurator.Configure(new FileInfo("log4net.config"));
            _logManager = new LogManager();
        }

        public static ILogger GetLogger<T>()
        {
            return _logManager.GetLogger(typeof(T));
        }

        public ILogger GetLogger(Type type)
        {
            var logger = log4net.LogManager.GetLogger(type);
            return new LoggerAdapter(logger);
        }
    }
}

下一步是创建这样的通用扩展,将调用者信息设置为ThreadContext.Property:

public static class GenericLoggingExtensions
{
    public static ILogger Log<TClass>(this TClass klass, [CallerFilePath] string file = "", [CallerMemberName] string member = "", [CallerLineNumber] int line = 0)
        where TClass : class
    {
        ThreadContext.Properties["caller"] = $"[{file}:{line}({member})]";
        return LogManager.GetLogger<TClass>();
    }
}

有了这个,我可以在编写实际方法之前调用记录器,只需调用:

this.Log().ErrorFormat("message {0} {1} {2} {3} {4}", "a", "b", "c", "d", "e");

如果您将PatternLayout的conversionPattern配置为使用属性:

<layout type="log4net.Layout.PatternLayout">
  <conversionPattern value="%utcdate [%thread] %-5level %logger - %message - %property{caller}%newline%exception" />
</layout>

你将始终拥有一个正确的输出,其中包含第一个.Log()的调用者信息 - 调用:

2017-03-01 23:52:06,388 [7] ERROR XPerimentsTest.CommonLoggingTests.CommonLoggingTests - message a b c d e - [C:\git\mine\experiments\XPerimentsTest\CommonLoggingTests\CommonLoggingTests.cs:71(Test_Debug_Overrides)]