Haacked (correctly) mentions in the comments of a previous post that using an attribute based system for the logging service I’m currently working on could be troublesome due to the fact that attribute parameters in .Net must be constants; i.e. you can’t use values provided in a configuration file as the compiler will complain loudly if you attempt to use a non-constant value. The reason for this is simple enough: Attributes are simply objects that get embedded into the assembly they are placed in; they provide metadata to the actual attribute object itself via either positional parameters (specified in the constructor) or named parameters (specified as read/write fields or properties). Therefore, parameters must be constant by design.
A little background first. Here’s the IL of some harness code I wrote testing out a custom attribute:
.method private hidebysig instance void button1_Click(object sender,
class [mscorlib]System.EventArgs e) cil managed
{
.custom instance void [Framework]Framework.LoggableAttribute::.ctor(bool,
valuetype [System]System.Diagnostics.TraceLevel) = ( 01 00 01 04 00 00 00 00 00 )
// Code size 29 (0x1d)
// snipped
} // end of method Form1::button1_Click
As should be quite apparent from the red text, the attribute parameter data is embedded into the assembly as a series of bytes. Thankfully, the wonders of reflection allow for easy reading of the bytes via a call to Attribute.GetCustomAttributes; once you have an instance of the attribute you’re looking for you can treat it just like any other .Net object and call properties/methods/etc to get the data you need. Here’s a short example of how to retrieve attribute data from an attribute placed on a MemberInfo element in a parent assembly (based off the code example here):
private static bool isLoggable(MemberInfo member)
{
LoggableAttribute loggable = (LoggableAttribute)Attribute.GetCustomAttribute(member,
typeof(LoggableAttribute), true);
if (loggable != null)
{
// traceLevel defined upstream as System.Diagnostics.TraceLevel;
traceLevel = loggable.TraceLevel;
return loggable.IsLoggable;
}
}
I realize this is all custom attribute 101 stuff, but it’s still important to realize that attributes are full blown objects just like everything else in .Net; it’s just a little trickier to get an object reference to the attribute itself in that you must use reflection to get access to the contextual information from the type (or member) the attribute is placed on (as Haacked also mentioned in his comment, which is where attributes really start to shine in a scenario like this) in this case. Powerful stuff indeed.
So the question still remains; how to get around having to recompile your code if you want to flip an attribute’s parameter value? The solution I’ve come up with for now is to basically allow values to be “overridden” in a configuration file, and just use that value instead in the core logging library with precedence given to the value in the configuration file (i.e. in this case if it’s false, use that value instead of the attribute’s boolean parameter…if it’s true, just ignore it and use the value specified in the attribute itself). In essence, the attribute allows you to mark sections of code by either:
- Setting the attribute parameter to false, which means always not logged, regardless of what the configuration file says to do. There are plenty of scenarios where logging may not be appropriate, such as basic UI type stuff. You could of course just leave the attribute off the member, but there are other constraints I’m building into the service that work in conjunction the attribute placement (which is outside the scope of this post for the time being).
- Set the configuration file setting to true, which lets the individual attributes figure out what to do.
Anyhow, that wraps up this post. This project continues to get more and more interesting by the day; the next item on my list is to implement a ContextBoundObject scheme to handle the heavy lifting of intercepting exception/method calls, and take care of all the logging behind the scenes with minimal code injection into existing applications. Mighty fun stuff.
Share this post: 
|

|

|

|

|
