问题 确保调用以结束一系列方法


注意/免责声明:经过几次搜索后,我在本帖中看到的最接近的内容是SO上的帖子(方法链接和整理问题)这与我的问题类似,但并没有真正回答 - 但无论如何,我希望这不是一个重复的问题。

我在做什么:

我已经在现有的日志记录框架上为一堆方法调用创建了一个流畅的界面 - 所以我的语法看起来有点像这样:

Logger.Debug().Message("Debug message!").WriteToLog();
Logger.Error().Message("An exception occured").Exception(ex).WriteToLog();

我将一个内部对象从一个方法调用传递给下一个对象,以便在最后调用时(WriteToLog方法);消息被写入某个日志文件。

我觉得有点闻

为了验证(仅当应用程序是在调试模式下构建的时候),我在上下文类(只是一个属性包对象)上有一个属性,它从方法调用传递给返回的对象,直到链终止;它是一个布尔值,默认为false。

使用Debug.Assert在上下文类析构函数中评估此属性,以确定是否调用结束链的最终方法,以便在开发期间捕获任何日志记录错误。 (属性,设置属性的代码和析构函数本身都是在#if DEBUG预处理器指令的上下文中创建的,所以如果它是在发布中构建的,或者如果符号不存在,则代码不会得到编译。)

一世 知道 在c#2.0及更高版本中使用析构函数是不好的,并且我可能无法访问属性,因为我相信对最终化顺序没有任何保证。这就是为什么它只在内置于调试模式时发生,以及为什么我想摆脱它。

我试图建立一个断言的原因是因为它很容易忘记并最终编写代码

Logger.Debug().Message("Debug message!");

这意味着没有任何东西被记录下来,虽然粗略地看了它应该是这样。

我的问题

我想知道的是 - 有人能想到另一种验证最终方法总是被调用的方法吗?在开发过程中只需要这些消息,以向开发人员强调方法链尚未完成 - 我不希望最终用户查找与登录最终产品相关的错误消息。


11231
2018-03-12 22:01


起源

为什么要使用流畅的API?简单来说有什么问题 Logger.Debug("Debug message!"); 和 Logger.Error("An exception occurred", ex);?在我看来,你正在使你的日志API比它需要的更复杂......流畅的API是一个很好的概念,但它们不是银弹,不要试图在任何地方使用它们只是因为你可以。 - Trevor Pilley
@Jay线索有“它造成了比这更多的问题”我建议扔掉它,只是使用log4net或NLog,专注于实际为产品增加价值,而不是浪费时间使用这个流畅的API,而不是需要它。 - Trevor Pilley
Install-Package NLog,找到并替换你将在半个早上完成:) - Trevor Pilley
@KellyGendron在这种情况下,您可能想要使用 Serilog 代替 - Trevor Pilley
@TrevorPilley - 请看一下,谢谢。然后有时我真的很喜欢重新发明轮子,给我周末做的事情:) - Kelly Gendron


答案:


首先,我会质疑在这种情况下是否需要流畅的界面,似乎你可以轻松地通过一个更简单的界面:

Logger.Debug.Message("Test");

甚至只是:

Logger.Debug("Test");

但是,如果您确实需要/想要一个流畅的界面,那么执行此操作的另一种方法是使fluent接口处理方法的参数,而不是返回值。

所以不要这样做:

Method1().Method2().Method3();

然后忘记了最后的电话:

Method1().Method2().Method3().Execute();

你会改为组织代码,也许是这样的:

Method1(o => o.Method2().Method3());

为此,您将定义一个对象,您将在其上调用所有流畅的方法:

public class LoggerOptions
{
    public LoggerOptions Debug() { LoggerType = LoggerType.Debug; return this; }
    public LoggerOptions Error() { LoggerType = LoggerType.Error; return this; }
    public LoggerOptions Message(string message) { ...; return this; }

    public LoggerType Type { get; set; }
    ...
}

此处的每个方法调用都将修改LoggerOptions对象,然后返回相同的实例,以继续流畅的接口。

接着:

public static class Logger
{
    public static void Log(Func<LoggerOptions, LoggerOptions> options)
    {
        LoggerOptions opts = options(new LoggerOptions());
        // do the logging, using properties/values from opts to guide you
    }
}

然后你会这样称呼它:

Logger.Log(opts => opts.Debug().Message("Debug message"));

如果你有一些终端方法,你必须在最终设置选项对象之前调用,你可以创建不同的对象:

public class LoggerOptions
{
    public LoggerOptions Debug() { LoggerType = LoggerType.Debug; return this; }
    public LoggerOptions Error() { LoggerType = LoggerType.Error; return this; }
    public LoggerOptions Message(string message) { ...; return this; }

    public LoggerType Type { get; set; }
    ...

    public LoggerFinalOptions ToEventLog() { ...; return new LoggerFinalOptions(this); }
    public LoggerFinalOptions ToFile(string fileName) { ...; return new LoggerFinalOptions(this); }
}

接着:

public static class Logger
{
    public static void Log(Func<LoggerOptions, LoggerFinalOptions> options)
    {
        LoggerFinalOptions opts = options(new LoggerOptions());
        // do the logging, using properties/values from opts to guide you
    }
}

这样可以保证在不通过调用返回显式最终选项对象的方法结束方法链的情况下编译代码:

// will not compile
Logger.Log(opts => opts.Debug().Message("Test"));

// will compile
Logger.Log(opts => opts.Debug().Message("Test").ToFile("log.log"));

11
2018-03-12 22:09



很好的答案,刚刚经过测试,这正是我想要的。通过稍微修改方法签名Log(Expression <Func <Option >> expr),我能够获得一个可验证的流畅接口,只有在启用了日志记录模式时才会编译(因此它只会执行字符串格式化等操作)。谢谢! - Jay