Создайте атрибут, который будет обрабатывать оформленный метод как делегат - PullRequest
0 голосов
/ 09 января 2020

Преамбула / Контекст

Так что в настоящее время у меня есть метод Log.

public T Log<T>(Func<T> func)
{
    string methodName = func.Method.Name;
    bool success = false;
    T product = default(T);

    Debug($"Entering {methodName}");

    try
    {
        product = func.Invoke();
        success = true;
    }
    catch (Exception e)
    {
        Info($"FAILURE: {methodName}: {e.Message}");
    }

    Debug($"{success.ToString().ToUpper()}: Exiting {methodName}");
    return product;
}

, который действительно хорошо работает для меня и называется так

Log(() => AddTwoNumbers(1, 2));

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

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ У меня нет легкого доступа к AOP в моей ситуации, поэтому создание прокси, который обрабатывает это, не простой путь. Я в основном переписываю устаревший код и обращаюсь к моим библиотекам. NET из EXE-файлов Visual Basi c 6, и, насколько я вижу, большинство из этих сред AOP должны быть включены непосредственно в приложение.

Проблема, которую я пытаюсь решить

Много раз я обнаруживаю, что делаю это:

public int AddFirstFourNumbers(params int[] numbahs) 
{
  Log(() => 
  {
    int product;
    product += numbahs[0];
    product += numbahs[1];
    product += numbahs[2];
    product += numbahs[3];

    return product;
  }
}

Или украшаем всех членов класса расширения следующим образом

public override int AddTwoNumbers(int num1, int num2)
{
  return Log(() => base.AddTwoNumbers(num1, num2));
}

и я хотел бы превратить это в это:

[Log]
public int AddFirstFourNumbers(params int[] numbahs) 
{
    int product;
    product += numbahs[0];
    product += numbahs[1];
    product += numbahs[2];
    product += numbahs[3];

    return product;
}

и это

[Log]
public override int AddTwoNumbers(int num1, int num2)
{
  return base.AddTwoNumbers(num1, num2);
}

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

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/attributes/creating-custom-attributes

В то же время мы имеют MVC атрибуты проверки, которые, кажется, предоставляют массу полезных функций, так что это может быть еще возможно .

Я большой поклонник Action & Fun c Wrappers, которые позволяют я украсить свои базовые методы и отделить мои проблемы. Я просто ищу более чистый способ сделать это. Было бы неплохо иметь возможность просто добавить [WithImpersonation] в начало метода или [UndoOnFailure] и отображать только базовую реализацию внутри фактического блока метода.

1 Ответ

3 голосов
/ 09 января 2020

Во-первых, я хочу поговорить о проблеме, которая возникнет у вас при использовании атрибутного подхода. Предположим, у вас есть метод AddFirstFourNumbers, украшенный атрибутом Log, как описано в вашем вопросе.

Атрибут существует как метаданные в методе. Вот и все. Любые содержащиеся в нем методы будут вызываться только в том случае, если вы их специально вызываете. Другими словами:

var attrib = typeof(ClassWithAddFirstFourNumbers)  // get the attribute
    .GetMethod(nameof(AddFirstFourNumbers))
    .GetAttributes<LogAttribute>()
    .FirstOrDefault();

if (attrib != null)  attrib.BeforeLog();
AddFirstFourNumbers(somenumbers);
if (attrib != null)  attrib.AfterLog();

Теперь вы можете использовать свой лямбда-подход, чтобы создать метод, который получает этот атрибут, но вам потребуется go подход дерева выражений (см. Ниже), чтобы получить атрибут изнутри лямбда-зов. И теперь у вас есть атрибут без реальной причины. Не весело.

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

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

Я думаю, вы злоупотребляете обработкой исключений. Я предлагаю вам ловить исключения на самом высоком уровне, где вы можете принять решение о том, как их обработать . Там у вас будет стек вызовов, который укажет, где произошло исключение. Как правило, нет необходимости регистрировать исключения там, где они произошли , если только вам не нужно регистрировать переменные, чтобы понять причину исключения. (KeyNotFoundException не говорит вам , какой ключ не был найден!) Система ведения журнала общего назначения, как вы предлагаете в своем вопросе, не могла - не могла - захватить эти переменные .

Это более субъективное мнение, но я чувствую, что вы немного обдумываете это. Это моя рекомендация. Сохраняйте это простым и просто добавьте в свои методы некоторые записи:

public int AddFirstFourNumbers(params int[] numbahs) 
{
    int product;
    product += numbahs[0];
    product += numbahs[1];

    Debug("Still adding numbers but we'll require additional pylons soon");

    product += numbahs[2];
    product += numbahs[3];
    return product;
}

И:

var sum = AddSomeNumbers(somenumbers);
Debug($"{nameof(AddSomeNumbers)} on {somenumbers.Length} numbers -> {sum}");

Это обеспечивает большую гибкость и полезность в ваших сообщениях журнала, и не мешает вашему код гораздо больше, чем лямбда-подход. Это означает, что вам нужно написать сообщение журнала, но нет правила, чтобы все они были последовательными и содержали полные советы по отладке. Что-то простое, например Debug($"sum={sum}");, все еще полезно и не требует много времени для ввода.

Хорошо. Эта последняя идея, вероятно, даст вам наибольшую выгоду от того, чего вы хотите достичь. У меня есть следующие условия, верно?

  • Вы хотите знать, откуда вы входите. Класс и метод. Даже номер строки.

  • Вы хотели бы знать поток или пользователя.

  • Вы хотели бы иметь возможность добавить дополнительную информацию о регистрации глобально без необходимости изменения вашего кода.

  • Вы хотели бы иметь возможность добавлять дополнительную информацию о регистрации в конкретизировать c классы или пространства имен , без необходимости изменения кода.

Используйте такие библиотеки, как NLog, log 4net, Serilog или другие. Они все очень хорошие. Все они позволят вам определить глобальный шаблон для ваших сообщений журнала: имя класса, имя метода, идентификатор потока, имя пользователя, метка времени, переменная среды и т. Д. c. Я больше всего знаком с NLog, который предоставляет их как рендеринг макетов . Я покажу вам, как это работает с помощью NLog.

public class YourExampleClass
{
    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();

    public void ExampleMethod()
    {
        try
        {
           Logger.Info("Hello world");
        }
        catch (Exception ex)
        {
           Logger.Error(ex, "Goodbye cruel world");
        }
    }
}  

С этим кодом другая указанная мной информация, такая как местоположение и метка времени, добавляется в конфигурацию с использованием средств визуализации макета. Аккуратно, а? Это не потребует масштабных изменений в вашей программе. Вы можете войти в классы, из которых вы хотите войти, и другие классы не будут изменены.

Для вашего удобства, вот официальный учебник NLog .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...