Custom Attributes with Extension Methods: Resource Key
I picked up this technique in my last job to use a custom attribute to contain a resource key. The biggest benefit was all the enums in the system used this attribute which provided a way to translate that enum to text. Take a look at a sample enum:
public enum Mode
{
[AttributeResourceKey("lblInvalid")]
Invalid,
[AttributeResourceKey("lblReview")]
Review,
[AttributeResourceKey("lblCheckout")]
Checkout,
[AttributeResourceKey("lblOrdered")]
Ordered
}
Each enum uses the AttributeResourceKey
to specify the resource key defined in the resx file. Combined with an Extension Method, we can extend the enum itself to allow us to execute the following:
public void DoOperation(Mode mode)
{
Log.Info(GetResourceString(mode.ResourceKey()));
...
}
The C++ head in me thinks, “why are we using Reflection when a static function in a helper class could contain a switch
statement to convert the enum to the resource key?”. Technically, this is sufficient and faster. However, the C# head in me loves the idea that the enum and the resource key are intimately tied together in the same file. There is no helper function to forget to update. The penalty of reading an attribute is a small price to pay to keep the enum and resource key together in order to increase overall maintainability.
So the first thing I am going to do is define a simple interface for my custom attributes.
public interface IAttributeValue<T>
{
T Value { get; }
}
All this interface does is define that the custom attribute class itself will define a property called Value
of type T
. This will be useful when using the generic method below for pulling the attribute. Next, we define the custom attribute class itself.
public sealed class AttributeResourceKey : Attribute, IAttributeValue<string>
{
private string _resourceKey;
public AttributeResourceKey(string resourceKey)
{
_resourceKey = resourceKey;
}
#region IAttributeValue<string> Members
public string Value
{
get { return _resourceKey; }
}
#endregion
}
Notice how simple the above class is. We have a constructor taking a string and a property called Value
which returns the said string. Now let’s look at the generic method for pulling the attribute.
public static class AttributeHelper
{
/// <summary>
/// Given an enum, pull out its attribute (if present)
/// </summary>
public static TReturn GetValue<TAttribute, TReturn>(object value)
where TAttribute: IAttributeValue<TReturn>
{
FieldInfo fieldInfo = value.GetType().GetField(value.ToString());
object[] attribs = fieldInfo.GetCustomAttributes(typeof(TAttribute), false);
TReturn returnValue = default(TReturn);
if (attribs != null && attribs.Length > 0)
returnValue = ((TAttribute)attribs[0]).Value;
return returnValue;
}
}
Above is the heart of the code. It uses Generics so you need only define this code once in a static class. By passing the attribute and return type, we can extract our Value
defined by IAttributeValue<TReturn>
. Using the where
constraint on TAttribute
allows the generic method to know this type defines a property called Value
of type TReturn
. This exposes the true power of Generics as without this constraint, the method could only presume TAttribute
is nothing more than an object. This might tempt you to wrongly cast TAttribute
in order to access its properties, inviting an exception only seen at runtime.
Now to define our Extension Method, to be placed in a common namespace, to extend all enums with the ResourceKey()
method.
public static class EnumerationExtensions
{
/// <summary>
/// Given an enum, pull out its resource key (if present)
/// </summary>
public static string ResourceKey(this Enum value)
{
return AttributeHelper.GetValue<AttributeResourceKey, string>(value);
}
}
Thanks to the generic attribute helper, the above Extension Method looks trivial. We simply use the helper to return the resource key and now we’ve extended all our enums to have this useful property.
Post Comment
Z5R4ff Thank you for your blog post.Much thanks again. Will read on...