【问题标题】:Ensuring that a call is made to end a chain of methods确保调用结束方法链
【发布时间】:2013-03-12 22:01:11
【问题描述】:

注意/免责声明:经过几次搜索后,我看到的最接近这篇文章的是 SO (Method chaining and the finishing problem) 上的一个帖子,这与我的问题相似,但并没有真正回答 - 但无论如何,我希望这不是一个重复的问题。

我在做什么:

我已经为一堆方法调用创建了一个流畅的接口作为现有日志框架的外观 - 所以我的语法看起来有点像这样:

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

这意味着什么都不会被记录,尽管粗略一看,它应该是应该的。

我的问题

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

【问题讨论】:

  • 为什么要有流畅的 API?简单的Logger.Debug("Debug message!");Logger.Error("An exception occurred", ex); 有什么问题?在我看来,你让你的日志 API 变得比它需要的更复杂......流利的 API 是一个很好的概念,但它们不是灵丹妙药,不要仅仅因为你可以就尝试在任何地方使用它们。
  • @Jay 的线索是“它导致的问题比这更多”我建议把它扔掉,只使用 log4net 或 NLog 并专注于实际为产品增加价值而不是浪费时间流利的 API 用于不需要它的东西。
  • Install-Package NLog,查找替换,半个上午就搞定了:)
  • @KellyGendron 在这种情况下,您可能想改用Serilog
  • @TrevorPilley - 会看看,谢谢。再说一次,有时我真的很喜欢重新发明轮子,让我在周末有事可做:)

标签: c# .net fluent-interface


【解决方案1】:

首先我会质疑在这种情况下是否需要一个流畅的界面,看起来你可以使用更简单的界面轻松解决:

Logger.Debug.Message("Test");

甚至只是:

Logger.Debug("Test");

但是,如果您真的需要/想要一个流畅的界面,另一种方法是让流畅的界面处理方法的参数,而不是返回值。

所以不要这样做:

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

【讨论】:

  • 很好的答案,刚刚测试过,这正是我想要的。通过对方法签名 Log(Expression> expr) 进行轻微修改,我能够获得一个可验证的流畅接口,如果启用了日志记录模式,它只会被编译(所以它只做字符串格式化之类的东西)。谢谢!
猜你喜欢
  • 2012-03-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-20
  • 2017-06-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多