views:

104

answers:

8

I'm using log4net, and we have a lot of this in our code:

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

One downside is that it means we're pasting this 10-word section all over, and every now and then somebody forgets to change the class name. The log4net FAQ also mentions this alternative possibility, which is even more verbose:

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

Is it possible to write a decorator to define this? I'd really like to say simply:

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

I've done similar things in other languages, but never a statically-compiled language like C#. Can I define class members from a decorator?

Edit: Heh. I'm a Lisp programmer. I appreciate the suggestions to switch languages, but really, if I was going to switch languages for better metaprogramming capabilities, I'd go all the way to Lisp instead of being half-assed about it. Unfortunately using a different language isn't an option on this project.

A: 

Man this would be a breeze in python. I would look into C# Attributes.

Bryan Ross
+1  A: 

Attributes in .NET are not decorators / active components that influence the class/member they're attached to. Attributes are meta-data that can be retrived with reflection. There is no decorator-like facility in C#, although there are AOP solutions that extend .NET with what you want. The simplest solution is probably to just copy that line to each class though.

dtb
That's true, and I misspoke. Still, I guess I was hoping for some kind of global initialization hook that could be used to convert attributes into members.
Ken
+4  A: 

This is exactly a task for AOP - Aspect Oriented Programming. Have a look at PostSharp, it's a .NET AOP framework, it will allow you to do exactly what you want.

This works by modifying (or weaving) IL code in post-compilation, and adding the logging aspect to the decorated method.

EDIT: It appears that PostSharp now is a commercial product. If you're looking for an open-source (free) solution, I suggest LinFu AOP.

hmemcpy
A: 

If this is ASP.NET or Windows Forms, you can just create a base Page or base Form that all your Forms/Pages derive from. You can declare this member at that level and probably achieve what you desire.

David
I'm not even entirely sure what those are. I'm just using plain C# here.
Ken
Subclassing a certain base class only to reduce the code by a line is *very* bad code smell! What if you need a class to subclass a different, given, base class? Look at Tim Jarvis' solution which at least doesn't require subclassing but uses an interface.
chiccodoro
A: 

You could probably implement a solution for this with the help of PostSharp, or another Aspect Oriented Programming tool.

It would be pretty trivial using a macro in Boo, but that wouldn't help your situation much.

JasonTrue
A: 

Perhaps an easy way would be to write an extension method on an Interface, then your class just need to "implement" (but not really because the extension does the impl) the interface

public class Foo : IGetLog<Foo>
{

}

The extension something like....

  public interface IGetLog<T> { }

  public static class IGetLogExtension
  {
    public static ILog GetLogger<T>(this IGetLog<T> getLog)
    {
      return LogManager.GetLogger(typeof(T));
    }
  }
Tim Jarvis
Interesting. That's not *too* bad. I'm not crazy about it since it's not *quite* DRY, though it is less typing, and the repetition is on the same line, so it's less likely to be mistakenly pasted with the wrong classname.
Ken
True, but remember an attribute solution also requires the line of code on each class you want to support.
Tim Jarvis
Force every class of a project to implement a certain interface only because we won't to log something in it smells bad in my nose.
chiccodoro
A: 

We wrapped log4net so we could switch it out with ease. It is something that we could very possibly change our mind on in the future.

While we are not doing this, and you will probably take a performance hit to do so....and I'm really hesitant to even suggest it because I'm not sure it is a good idea.....you could...if you felt devilish enough....wrap log4net and do something like this in your wrapper.

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

If you're smart about how you're logging you could only incur that cost when you need the log message.

Mark
This program is a front-end for a (pretty hefty) database. I'm almost positive that even fast database queries are going to dominate any performance profile. :-)
Ken
@Ken You would be, on a worst case, generating a stack trace for every log message. You could probably use the same method to initialize in a static constructor like you showed, and avoid that per message expense.
Mark
Mark, IMHO log4net *is* actually nothing else than a wrapper - a wrapper around a predefined implementation delivered in the library, which you can choose to replace by your own whenever you want.
chiccodoro
+1  A: 

We use something almost identical:

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

This method is implemented as follows:

[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 { }

It still proves to be more of a pain that I'm willing to go through. I much prefer a static object with static methods for logging. Obtaining the calling method is not that expensive provided your not getting file info. Compared to the expense of just calling Log4Net obtaining the calling type is nothing (depending some on the loggers used).

csharptest.net
Interesting! I'm definitely tempted to do something like this now.
Ken