I'm wanting to create a Reusable class to work on Enumerations so that I can decorate various Enums with different attributes and I can use this class to get a particular attribute property to display.
An UnitTest example of how I would use the class is:
internal enum TestEnum
{
[System.ComponentModel.Description("Description Attribute")]
[System.Xml.Serialization.XmlAttribute("XML Attribute")]
EnumWithDescription,
[System.ComponentModel.Category("Display name attribute")]
[System.ComponentModel.Description("Description Attribute over category attribute")]
EnumWithXMLAttribute,
EnumNoAttributes,
}
protected EnumAttributeDescriptor CreateEnumAttributeDescriptor()
{
EnumAttributeDescriptor descriptor = new EnumAttributeDescriptor();
descriptor.AddAttributePriority<System.ComponentModel.DescriptionAttribute>("Description");
descriptor.AddAttributePriority(new PropertyDecoration("Category", typeof(System.ComponentModel.CategoryAttribute)));
return descriptor;
}
internal class IntAttributeDescriptor : PropertyAttributeDescriptor
{
public IntAttributeDescriptor()
: base()
{
AddAttributePriority<System.ComponentModel.DescriptionAttribute>("Description");
}
}
internal class EnumSwitchableDescriptor : IObjectDescriptor
{
public string GetDescription(object record)
{
TestEnum item = (TestEnum)record;
switch (item)
{
case TestEnum.EnumWithDescription:
return "This is a test attribute";
case TestEnum.EnumWithXMLAttribute:
return "How did you sneak in here";
default:
throw new NotSupportedException(string.Format("Enumeration not supported {0}", item));
}
}
}
internal class TestObject
{
[System.ComponentModel.Description("Testable integer")]
public int TestInteger
{
get;
set;
}
}
[TestMethod]
public void EnumDescriptionHardCodedTest()
{
ObjectDescription descriptions = new ObjectDescription(new EnumSwitchableDescriptor());
string name = descriptions.GetDisplayName(TestEnum.EnumWithDescription);
Assert.AreEqual("This is a test attribute", name);
}
[TestMethod]
public void EnumDescriptionMissingAttributeTest()
{
ObjectDescription descriptions = new ObjectDescription(CreateEnumAttributeDescriptor());
string name = descriptions.GetDisplayName(TestEnum.EnumNoAttributes);
Assert.IsNull(name);
}
[TestMethod]
public void EnumDescriptionDefaultAttributeTest()
{
ObjectDescription descriptions = new ObjectDefaultableDescription((obj) => { return null; });
string name = descriptions.GetDisplayName(TestEnum.EnumWithDescription);
Assert.AreEqual("EnumWithDescription", name);
}
[TestMethod]
public void EnumDescriptionFromSingleAttributeTest()
{
ObjectDescription descriptions = new ObjectDescription(CreateEnumAttributeDescriptor());
string name = descriptions.GetDisplayName(TestEnum.EnumWithDescription);
Assert.AreEqual("Description Attribute", name);
}
[TestMethod]
public void EnumDescriptionFromObjectTest()
{
ObjectDescription descriptions = new ObjectDescription(CreateEnumAttributeDescriptor());
TestEnum testable = TestEnum.EnumWithXMLAttribute;
string name = descriptions.GetDisplayName(testable);
Assert.AreEqual("Description Attribute over category attribute", name);
}
[TestMethod]
public void IntDescriptionFromObjectTest()
{
TestObject testable = new TestObject();
ObjectDescription descriptor = new ObjectDescription(new IntAttributeDescriptor());
string description = descriptor.GetDisplayName(testable);
Assert.AreEqual("Testable integer", description);
}
My ObjectInspector class responsible for getting a property :
public class ObjectInspector
{
public string GetProperty(object record, string propertyName)
{
Type typeOf = record.GetType();
object value = typeOf.GetProperty(propertyName).GetValue(record, null);
if (value == null)
return null;
else
return value.ToString();
}
public bool IsOfType(object record, Type typeOf)
{
Type typeOfRecord = record.GetType();
return typeOfRecord.Equals(typeOf) || typeOfRecord.IsSubclassOf(typeOf);
}
public bool IsOfType<T>(object record)
{
Type typeOf = typeof(T);
return IsOfType(record, typeOf);
}
}
My Descriptor class and interface:
public interface IObjectDescriptor
{
string GetDescription(object record);
}
public abstract class AttributeDescriptor : IObjectDescriptor
{
private readonly List<PropertyDecoration> _attributes;
public AttributeDescriptor()
{
_attributes = new List<PropertyDecoration>();
}
public string GetDescription(object record)
{
var priorities = GetAttributes();
if (priorities == null || record == null)
return null;
var attributes = GetAttributes(record);
if (attributes != null)
return GetAttributeDescription(attributes);
else
return null;
}
public virtual void AddAttributePriority(PropertyDecoration attr)
{
_attributes.Add(attr);
}
public virtual void AddAttributePriority<T>(string propertyName)
{
PropertyDecoration lookup = new PropertyDecoration(propertyName, typeof(T));
AddAttributePriority(lookup);
}
public virtual IEnumerable<PropertyDecoration> GetAttributes()
{
return _attributes;
}
protected abstract object[] GetAttributes(object record);
protected virtual string GetAttributeDescription(object[] attributes)
{
ObjectInspector inspector = new ObjectInspector();
object attribute = null;
// looping from highest precedence
foreach (var attr in GetAttributes())
{
// find a matching attribute on our object
attribute = attributes.FirstOrDefault(p => inspector.IsOfType(p, attr.GetPropertyType()));
if (attribute != null)
{
//return attribute;
return attr.GetDescription(attribute);
}
}
return null;
}
}
public class EnumAttributeDescriptor : AttributeDescriptor
{
protected override object[] GetAttributes(object record)
{
var typeOf = record.GetType();
var info = typeOf.GetMember(record.ToString());
return info[0].GetCustomAttributes(false);
}
}
And finally the Object description classes to actually get the description:
public class ObjectDescription
{
private IObjectDescriptor _objectDescriptor;
public ObjectDescription(IObjectDescriptor attributePriorities)
{
_objectDescriptor = attributePriorities;
}
public string GetDisplayName(object record)
{
return GetDescription(record, _objectDescriptor);
}
protected virtual string GetDescription(object record, IObjectDescriptor descriptor)
{
return descriptor.GetDescription(record);
}
}
public class ObjectDefaultableDescription : ObjectDescription
{
private Func<object, string> _defaultIfEmpty;
public ObjectDefaultableDescription(Func<object, string> defaultIfEmpty)
: base(null)
{
_defaultIfEmpty = defaultIfEmpty;
}
protected override string GetDescription(object record, IObjectDescriptor descriptor)
{
if (record == null)
return _defaultIfEmpty(record);
else
return record.ToString();
}
private string GetDefaultIfEmpty(object record)
{
if (_defaultIfEmpty != null)
return _defaultIfEmpty(record);
else
return null;
}
}
I want to keep it flexible so that I can decorate Enums with different Attributes as the requirements may vary i.e. using System.Xml.Serialization.XmlEnumAttribute in conjunction with System.ComponentModel.DescriptionAttribute so a UI can use the Description attribute to display a nice name while an XML serialiser can use the XmlEnumAttribute etc
I would expect to either provide a wrapper class for this on my various projects so that I wouldn't have to create the delegate method everywhere however unsure of this approach.
EDIT: After re-looking I thought the GetProperty() method is pretty generic and doesn't really necessary only belong to an Enum so I moved that out into it's own ObjectInspector class.
EDIT: After some further thought into this I decided that actually it's not directly related to Enum's but rather the ability to get descriptions from properties in general. I've made some tweaks to the classes to try and separate this out. Any comments would be most appreciated.
public static string GetDescription<T>(Enum en) where T : something
instead? Instead ofreturn en.ToString();
you can doreturn null;
as a way to indicate that this particular attribute was missing. Once this works, you can add wrapper methods which provide even more convenient ways of getting the info you want. \$\endgroup\$public static GetCategory(enum) { return GetCustomAttributevalue<CategoryAttribute>(enum, "Category");}
and so on. I do not likeGetEnumAttribute
- it decides what you want for you. I want to be able to ask for a specific attribute and get a null if it is not there. Then I can add a method on top of this that will look for the first non-null attribute string in some prioritized order. \$\endgroup\$