【问题标题】:Class decorator to declare static member (e.g., for log4net)?声明静态成员的类装饰器(例如,对于 log4net)?
【发布时间】:2011-02-20 15:05:15
【问题描述】:

我正在使用 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,而不是半途而废。不幸的是,在这个项目中不能选择使用不同的语言。

【问题讨论】:

    标签: c# decorator static-members


    【解决方案1】:

    伙计,这在 python 中将是一件轻而易举的事。我会调查C# Attributes.

    【讨论】:

      【解决方案2】:

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

      【讨论】:

      • 是的,我说错了。尽管如此,我想我还是希望有某种全局初始化钩子可以用来将属性转换为成员。
      【解决方案3】:

      这正是 AOP - Aspect Oriented Programming 的任务。看看PostSharp,它是一个.NET AOP 框架,它可以让你做你想做的事。

      这通过在编译后修改(或编织)IL 代码,并将日志记录方面添加到修饰方法来实现。

      编辑: PostSharp 现在似乎是一种商业产品。如果您正在寻找开源(免费)解决方案,我建议您使用LinFu AOP

      【讨论】:

        【解决方案4】:

        如果这是 ASP.NET 或 Windows 窗体,您可以创建一个基本页面或基本窗体,所有窗体/页面都从该页面派生。您可以在该级别声明此成员,并可能实现您想要的。

        【讨论】:

        • 我什至不完全确定那些是什么。我只是在这里使用普通的 C#。
        • 子类化某个基类只是为了减少一行代码是非常糟糕的代码气味!如果您需要一个类来继承一个不同的给定基类怎么办?看看 Tim Jarvis 的解决方案,它至少不需要子类化而是使用接口。
        【解决方案5】:

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

        在 Boo 中使用宏非常简单,但这对您的情况没有多大帮助。

        【讨论】:

          【解决方案6】:

          也许一种简单的方法是在接口上编写扩展方法,然后你的类只需要“实现”(但不是真的因为扩展实现了)接口

          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,尽管它打字较少,并且重复在同一行上,所以它不太可能被错误地粘贴到错误的类名。跨度>
          • 是的,但请记住,属性解决方案还需要您要支持的每个类上的代码行。
          • 强制项目的每个类都实现某个接口,只是因为我们不会在其中记录一些东西,它在我的鼻子里闻起来很臭。
          【解决方案7】:

          我们包装了 log4net,以便我们可以轻松地将其切换出去。这是我们将来很可能改变主意的事情。

          虽然我们不这样做,但这样做可能会对性能造成影响......而且我什至不敢提出建议,因为我不确定这是一个好主意...... .你可以......如果你觉得足够邪恶......包装 log4net 并在你的包装器中做这样的事情。

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

          如果您对日志记录方式很了解,则只有在需要日志消息时才会产生该成本。

          【讨论】:

          • 这个程序是一个(相当庞大的)数据库的前端。我几乎可以肯定,即使是快速的数据库查询也将主导任何性能配置文件。 :-)
          • @Ken 在最坏的情况下,您会为每条日志消息生成堆栈跟踪。您可能可以使用相同的方法在您展示的静态构造函数中进行初始化,并避免每条消息的费用。
          • 马克,恕我直言,log4net 实际上只不过是一个包装器 - 库中提供的预定义实现的包装器,您可以随时选择自己替换.
          • @chiccodoro 这是真的。当我们为 Hoptoad 和 Growl 创建自定义 log4net 附加程序而不是基于我们的抽象的新子类实现时,我们发现了这一点。
          【解决方案8】:

          我们使用几乎相同的东西:

          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 获取调用类型的开销不算什么(取决于使用的一些记录器)。

          【讨论】:

          • 有趣!我现在肯定很想做这样的事情。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2018-01-02
          • 2011-09-27
          • 1970-01-01
          • 2010-10-25
          • 1970-01-01
          • 1970-01-01
          • 2011-02-24
          相关资源
          最近更新 更多