Как бы вы реорганизовали этот вонючий код? (Регистрация, копирование и вставка,.Net 3.5)

У меня есть код вроде этого:

Logger logger = new Logger();
System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch();
logger.LogInformation("Calling SomeObject.SomeMethod at " + DateTime.Now.ToString());
stopWatch.Start();
// This is the method I'm interested in.
SomeResponse response = someObject.SomeMethod(someParam);
stopWatch.Stop();
logger.LogInformation("SomeObject.SomeMethod returned at " + DateTime.Now.ToString());
logger.LogInformation("SomeObject.SomeMethod took " + stopWatch.ElapsedMilliseconds + " milliseconds.");

Мне нужно обернуть аналогичный код вокруг гораздо большего количества объектов и их методов, чтобы выполнить профилирование производительности. Мне не разрешено использовать сторонние плагины или программное обеспечение и т.д.

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

Если я не очень ясен, задайте вопросы в комментариях, и я попытаюсь уточнить.

Спасибо за любую помощь!

Ответ 1

Вы можете реорганизовать код, чтобы принять экземпляр указателя метода (aka System.Action).

public void CallWithLogTiming (Action theAction)
{
  Logger logger = new Logger();
  System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch();
  logger.LogInformation("Calling SomeObject.SomeMethod at " + DateTime.Now.ToString());
  stopWatch.Start();

// This is the method I'm interested in.
  theAction();

  stopWatch.Stop();
  logger.LogInformation("SomeObject.SomeMethod returned at " + DateTime.Now.ToString());
  logger.LogInformation("SomeObject.SomeMethod took " + stopWatch.ElapsedMilliseconds + " milliseconds.");
}

Затем вы можете вызвать его, создав лямбда-выражение. Поскольку myResponse является захваченной переменной, она будет заполнена, когда это действие будет запущено, и myResponse будет доступен для использования позже в этой области.

SomeResponse myResponse = null;
CallWithLogTiming( () => myResponse = someObject.SomeMethod(someParam) );

Ответ 2

Для простоты вы можете использовать дженерики, как это (с верхней части головы):

public T MyLogMethod<T,S>(Func<S, T> someFunction, S someParameter) {}

Func (S, T), где S - тип параметра метода, а T - тип возвращаемого значения.

Ответ 3

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

void test()
{
  foo();
  //timer for the following statements
  using (new MyTimer("Some method"))
  {
    bar();
  }
  baz();
}

Класс MyTimer реализуется следующим образом:

  • Содержит экземпляр стоп-часов и строку сообщения, которая идентифицирует таймер
  • Конструктор запускает секундомер и запоминает строку сообщения
  • Метод Dispose останавливает секундомер и регистрирует строку сообщения и время эллипса

Ответ 4

Определенно кандидат на АОП. Мы используем PostSharp для такого рода вещей. http://www.postsharp.org/

Ответ 5

Всегда есть библиотека PostSharp, которая позволяет делать аспектно-ориентированный код. Он позволяет выполнять регистрацию и секундомер в качестве атрибута, что очень круто. Он будет вводить pre и post code в ваш метод как шаг после компиляции.

Кроме того, вы можете рассмотреть некоторые статические методы таймера/регистратора, подобные этому, чтобы обернуть код, который вы хотите использовать для времени/журнала:

Timer.LogExecutionTime("SomeObject.SomeMethod", () =>
{
    Logger.LogBeforeAndAfter("SomeObject.SomeMethod", () =>
    {
        SomeResponse response = someObject.SomeMethod(someParam);
    }
});

Ответ 6

Вы можете сделать синтаксис немного более чистым с помощью некоторых простых методов расширения в Logger, для этого не требуется никаких дополнительных сборок, чем то, что у вас уже есть, и вы можете сразу подключить их. И это можно использовать повторно, если вы собираетесь делать это несколько раз на протяжении всего кода.

public static class LoggerExtentions
{
    public static void StartTimerLogInformation(this Logger logger, Stopwatch stopWatch, string method)
    {
        stopWatch.Reset();
        stopWatch.Start();
        logger.LogInformation(string.Format("Calling {0} at {1}", method, DateTime.Now.ToString()));
    }        

    public static void StopTimerLogInformation(this Logger logger, Stopwatch stopWatch, string method)
    {
        stopWatch.Stop();
        logger.LogInformation(string.Format("{0} returned at {1}", method, DateTime.Now.ToString()));
        logger.LogInformation(string.Format("{0} took {1} milliseconds", method, stopWatch.ElapsedMilliseconds));
        stopWatch.Reset();
    }
}

то вы можете просто использовать этот код взамен кода, который у вас есть в вашем исходном сообщении

Logger logger = new Logger();
Stopwatch stopWatch = new Stopwatch();
logger.StartTimerLogInformation(stopWatch, "SomeObject.SomeMethod");
SomeResponse response = someObject.SomeMethod(someParam);
logger.StopTimerLogInformation(stopWatch, "SomeObject.SomeMethod");

Ответ 7

Если вы пишете такой класс (я использую Java, могут быть некоторые вещи, которые не переводят точно):

public class myProfiler {
    final String method;
    final Logger logger = new Logger();
    final System.Diagnostics.Stopwatch stopWatch = new Stopwatch();
    public myProfiler(method) {this.method = method};
    public void Start() {
        logger.LogInformation("Calling " + method + " at " + DateTime.Now.ToString());
        stopWatch.Start();      
    }
    public void Stop() {
        stopWatch.Stop();
        logger.LogInformation(method + " returned at " + DateTime.Now.ToString());
        logger.LogInformation(method + " took " + stopWatch.ElapsedMilliseconds + " milliseconds.");
    }
}

Затем вы уменьшили код, который вам нужен в каждом методе, просто

myProfiler profiler = new myProfiler("SomeObject.SomeMethod");
profiler.Start();
...
profiler.Stop();

Ответ 8

Как вы наследуете класс таймера на всех ваших объектах?

Ответ 9

То, что мне нравится делать в этих случаях, - это реализовать мои таймеры с использованием шаблона "Одноразовый"; вам будет гарантирована правильная очистка и регистрация в случае ошибки:

    public class Timer : IDisposable
    {
        Logger logger = new Logger();
        Stopwatch stopWatch = new Stopwatch();

        public Timer()
        {
            calledFunc = CalledFunc;
            logger.LogInformation("Calling SomeObject.SomeMethod at " +
                DateTime.Now.ToString());
            stopWatch.Start();
        }

        // Dispose() calls Dispose(true)
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        // NOTE: Leave out the finalizer altogether if this class doesn't 
        // own unmanaged resources itself, but leave the other methods
        // exactly as they are. 
        ~Timer()
        {
            // Finalizer calls Dispose(false)
            Dispose(false);
        }
        // The bulk of the clean-up code is implemented in Dispose(bool)
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // free managed resources
                stopWatch.Stop();
                logger.LogInformation("SomeObject.SomeMethod returned at " +
                    DateTime.Now.ToString());
                logger.LogInformation("SomeObject.SomeMethod took " +
                    stopWatch.ElapsedMilliseconds + " milliseconds.");
            }
            // free native resources if there are any.
        }
    }

Затем вы используете таймер следующим образом:

using (var t = new Timer())
{
   // your code
}

Конечно, вы можете передать args таймеру (имя метода, регистратор и т.д.), чтобы настроить, что происходит в настройке и удалять