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 enum
s 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
{
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 enum
s with the ResourceKey()
method.
public static class EnumerationExtensions
{
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 enum
s to have this useful property.