Декоратор класса для объявления статического члена (например, для log4net)?

Я использую log4net, и в нашем коде много такого:

public class Foo {
    private static readonly ILog log = LogManager.GetLogger(typeof(Foo));
    ....
}

Единственным недостатком является то, что это означает, что мы вставляем этот раздел из 10 слов повсюду, и время от времени кто-то забывает изменить имя класса. В часто задаваемых вопросах log4net также упоминается эта альтернативная возможность, которая еще более многословно:

public class Foo {
    private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
    ...
}

Можно ли написать декоратор, чтобы определить это? Я бы очень хотел сказать просто:

[LogMe]  // or perhaps: [LogMe("log")]
public class Foo {
    ...
}

Я делал подобные вещи на других языках, но никогда на статически компилируемых языках, таких как C#. Могу ли я определить членов класса из декоратора?

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


person Ken    schedule 17.05.2010    source источник


Ответы (8)


Это как раз задача для АОП - аспектно-ориентированного программирования. Взгляните на PostSharp, это платформа .NET AOP, она позволит вам делать именно то, что вам нужно. хочу.

Это работает путем изменения (или переплетения) IL-кода после компиляции и добавления аспекта ведения журнала к декорированному методу.

EDIT: похоже, что PostSharp теперь является коммерческим продуктом. Если вы ищете решение с открытым исходным кодом (бесплатное), я предлагаю LinFu AOP< /а>.

person Igal Tabachnik    schedule 17.05.2010

Мы используем что-то почти идентичное:

private static readonly ILog Logger = LogManager.GetLogger();

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

[MethodImpl(MethodImplOptions.NoInlining)]
public static ILog GetLogger()
{
    Type t;
    try { t = new StackFrame(1, false).GetMethod().DeclaringType; }
    catch { t = typeof(UnknownObject); }
    return GetLogger(t);
}
private static class UnknownObject { }

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

person csharptest.net    schedule 17.05.2010
comment
Интересно! У меня определенно есть соблазн сделать что-то подобное сейчас. - person Ken; 18.05.2010

Атрибуты в .NET не являются декораторами/активными компонентами, влияющими на класс /member, к которому они привязаны. Атрибуты — это метаданные, которые можно получить с помощью отражения. В C# нет средства, подобного декоратору, хотя есть АОП-решения, которые расширяют .NET тем, что вам нужно. Самое простое решение, вероятно, просто скопировать эту строку в каждый класс.

person dtb    schedule 17.05.2010
comment
Это правда, и я оговорился. Тем не менее, я думаю, я надеялся на какой-то глобальный хук инициализации, который можно было бы использовать для преобразования атрибутов в члены. - person Ken; 18.05.2010

Мы обернули log4net, чтобы его можно было легко отключить. Это то, о чем мы, возможно, изменим свое мнение в будущем.

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

var callingType = new System.Diagnostics.StackTrace().GetFrame(1).GetMethod().DeclaringType

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

person Mark    schedule 17.05.2010
comment
Эта программа является интерфейсом для (довольно большой) базы данных. Я почти уверен, что даже быстрые запросы к базе данных будут доминировать над любым профилем производительности. :-) - person Ken; 18.05.2010
comment
@Ken В худшем случае вы будете генерировать трассировку стека для каждого сообщения журнала. Вероятно, вы могли бы использовать тот же метод для инициализации в статическом конструкторе, как вы показали, и избегать затрат на каждое сообщение. - person Mark; 18.05.2010
comment
Марк, ИМХО, log4net есть на самом деле не что иное, как обертка - обертка вокруг предопределенной реализации, поставляемой в библиотеке, которую вы можете заменить своей собственной, когда захотите. - person chiccodoro; 08.10.2010
comment
@chiccodoro Это правда. Мы обнаруживаем это, когда создаем собственные приложения log4net для Hoptoad и Growl вместо новой реализации подкласса, основанной на нашей абстракции. - person Mark; 17.11.2010

Чувак, это было бы легко в питоне. Я бы посмотрел на атрибуты C#.

person rossipedia    schedule 17.05.2010

Если это ASP.NET или Windows Forms, вы можете просто создать базовую страницу или базовую форму, из которой происходят все ваши формы/страницы. Вы можете объявить этого члена на этом уровне и, вероятно, добиться желаемого.

person David    schedule 17.05.2010
comment
Я даже не совсем уверен, что это такое. Я просто использую здесь простой C#. - person Ken; 18.05.2010
comment
Создание подкласса определенного базового класса только для сокращения кода на одну строку — это очень неприятный запах кода! Что, если вам нужен класс для подкласса другого заданного базового класса? Посмотрите на решение Тима Джарвиса, которое, по крайней мере, не требует создания подклассов, но использует интерфейс. - person chiccodoro; 08.10.2010

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

Было бы довольно тривиально использовать макрос в Boo, но это не сильно помогло бы в вашей ситуации.

person JasonTrue    schedule 17.05.2010

Возможно, простым способом было бы написать метод расширения для интерфейса, тогда вашему классу просто нужно «реализовать» (но не совсем потому, что расширение выполняет реализацию) интерфейс

public class Foo : IGetLog<Foo>
{

}

Расширение что-то вроде....

  public interface IGetLog<T> { }

  public static class IGetLogExtension
  {
    public static ILog GetLogger<T>(this IGetLog<T> getLog)
    {
      return LogManager.GetLogger(typeof(T));
    }
  }
person Tim Jarvis    schedule 17.05.2010
comment
Интересно. Это не слишком плохо. Я не в восторге от этого, так как он не совсем СУХОЙ, хотя он менее типичен и повторяется в одной и той же строке, поэтому вероятность того, что он будет ошибочно вставлен с неправильным именем класса, меньше. - person Ken; 18.05.2010
comment
Верно, но помните, что для решения атрибутов также требуется строка кода для каждого класса, который вы хотите поддерживать. - person Tim Jarvis; 18.05.2010
comment
Заставлять каждый класс проекта реализовывать определенный интерфейс только потому, что мы не будем что-то регистрировать, это плохо пахнет в моем носу. - person chiccodoro; 08.10.2010