类装饰器如何声明静态成员(例如log4net)?

5

我正在使用log4net,在我们的代码中有很多这样的东西:

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

缺点之一是这意味着我们会在很多地方粘贴这个10个单词的部分,偶尔会有人忘记更改类名。 log4net FAQ 还提到了另一种可能性,那就更冗长了:

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

是否有可能编写一个装饰器来定义它?我想要简单地说:

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

我之前在其他语言中做过类似的事情,但从未在像C#这样的静态编译语言中定义过类成员。我能否从装饰器中定义类成员?

编辑:嘿,我是Lisp程序员。感谢建议我换一种语言,但实际上,如果我要为了更好的元编程能力而换语言,我会直接转向Lisp,而不是半途而废。不幸的是,在这个项目中使用不同的语言不是一个选项。

8个回答

5
这正是AOP(面向切面编程)的任务。看一下PostSharp,它是一个.NET AOP框架,可以让您完全按照您想要的方式进行操作。
这通过在后编译中修改(或编织)IL代码,并将日志记录方面添加到装饰方法来实现。
编辑:看起来PostSharp现在是商业产品。如果您正在寻找开源(免费)解决方案,我建议使用LinFu AOP

3
我们使用几乎相同的东西:
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相比,获取调用类型很廉价(这取决于所使用的记录器)。


有趣!我现在肯定会被诱惑去做这样的事情。 - Ken

1

.NET中的属性不是装饰器或活动组件,它们不会影响所附加到的类/成员。 属性是元数据,可以通过反射检索。在C#中没有类似于装饰器的功能,尽管有AOP解决方案可以扩展.NET以实现您想要的功能。最简单的解决方案可能只是将该行复制到每个类中。


没错,我说错了。不过,我想我还是希望有一种全局初始化钩子,可以用来将属性转换为成员变量。 - Ken

1

我们封装了log4net,这样我们就可以轻松地切换它。未来我们很可能会改变这个想法。

虽然我们现在没有这么做,而且你可能会因此受到性能影响......我真的很犹豫甚至不敢建议这样做,因为我不确定这是否是一个好主意......但如果你感觉够胆,你可以封装log4net并在你的封装中做类似于这样的事情。

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

如果您在记录日志方面很聪明,那么只有在需要记录日志消息时才会产生成本。


这个程序是一个(相当庞大的)数据库的前端。我几乎可以确定,即使是快速的数据库查询也将占据任何性能概要。 :-) - Ken
@Ken 在最糟糕的情况下,你可能会为每个日志消息生成一个堆栈跟踪。你可以使用静态构造函数中展示的相同方法进行初始化,避免每条消息带来的开销。 - Mark
在我看来,log4net实际上就是一个包装器 - 一个围绕库中预定义实现的包装器,您可以选择在任何时候替换为自己的实现。 - chiccodoro
@chiccodoro 这是真的。我们正在发现这一点,因为我们正在为Hoptoad和Growl创建自定义log4net附加程序,而不是基于我们的抽象实现的新子类化实现。 - Mark

0
也许一种简单的方法是在接口上编写扩展方法,然后您的类只需要“实现”(但实际上不需要,因为扩展会执行实现)该接口。
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));
    }
  }

有趣。这不算糟糕。虽然它不是完全DRY,但我并不是很喜欢它,尽管它少打了一些字,而且重复出现在同一行,所以不太可能因为类名错误而被错误地粘贴。 - Ken
真的,但请记住一个属性解决方案还需要在您想支持的每个类上的代码行。 - Tim Jarvis
强制每个项目类只实现特定接口,因为我们想在其中记录某些内容,这让我感到不舒服。 - chiccodoro

0

这在 Python 中将会非常轻松。我会研究一下 C# 属性


0
如果这是ASP.NET或Windows Forms,您可以创建一个基本的Page或Form,所有的Forms/Pages都从中派生。您可以在该级别声明此成员,并可能实现您所需的功能。

我甚至不确定那些是什么。我只是在使用纯C#。 - Ken
仅为了减少一行代码而对某个基类进行子类化是非常糟糕的代码味道!如果您需要一个类来子类化不同的给定基类,该怎么办?看看Tim Jarvis的解决方案,它至少不需要子类化,而是使用接口。 - chiccodoro

0

你可以借助PostSharp或其他面向切面编程工具来实现此解决方案。

使用Boo中的宏可能非常简单,但这对你的情况没有什么帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接