Обеспечение того, чтобы был сделан вызов для прекращения цепочки методов

Примечание/Отказ от ответственности: после нескольких поисков, ближайшая вещь, которую я имею, я видел на этом посту, - это сообщение о SO (Цепочка метода и проблема финализации) который похож на мой вопрос, но на самом деле не отвечает на него, но в любом случае я надеюсь, что это не дублирующий вопрос.

Что я делаю:

Я создал свободный интерфейс в виде фасада поверх существующей структуры ведения журнала для кучи вызовов методов, поэтому мой синтаксис выглядит примерно так:

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

Я передаю внутренний объект от одного вызова метода к следующему объекту, так что когда выполняется последний вызов (метод WriteToLog); сообщение записывается в файл журнала где-то.

Бит, я думаю, пахнет

Чтобы проверить (только когда приложение построено в режиме отладки), у меня есть свойство в классе контекста (просто объект мешка свойства), который передается из вызова метода возвращенному объекту до тех пор, пока цепочка не завершится; он является логическим, а по умолчанию - false.

Это свойство оценивается в деструкторе класса контекста, используя Debug.Assert, чтобы определить, вызван ли последний метод для завершения цепочки, поэтому во время разработки могут быть отобраны ошибки регистрации. (свойство, код, который устанавливает свойство и сам деструктор, все создается в контексте предпроцессорной директивы #if DEBUG, поэтому, если он построен в версии или если символ не существует, код не будет скомпилировать.)

Я знаю, что использование деструктора плохо в С# 2.0 и выше, и что у меня может не быть доступа к свойствам, потому что я считаю, что нет никаких гарантий относительно порядка завершения. Вот почему это происходит только при построении в режиме отладки и почему я хотел бы уйти от него.

Причина, по которой я пытаюсь построить утверждение, заключается в том, что очень легко забыть и в конечном итоге написать код, например

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

что означает, что ничего не регистрируется, хотя на беглом взгляде это выглядит так, как должно.

Мой вопрос

Я хочу знать, может ли кто-нибудь подумать о другом способе проверки того, что последний метод всегда называется? Эти сообщения просто необходимы во время разработки, чтобы выделить разработчику, что цепочка методов еще не закончена - я не хочу, чтобы конечные пользователи находили сообщения об ошибках, связанные с протоколированием в конечном продукте.

Ответ 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"));

Если у вас есть какие-то терминальные методы, вам абсолютно необходимо позвонить до завершения настройки объекта options, вы можете создавать разные объекты:

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
    }
}

Тогда это гарантировало бы, что вы не сможете скомпилировать код, не заканчивая цепочку методов вызовом того, что возвращает явный окончательный объект options:

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

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