Reflection allows data to be determined at runtime by levereging the PortableExecutable file (PE) having metadata information from MSIL and IL. In this example I will show you how to categorize class types using class Attributes.
Attributes provide a powerful method of associating metadata, or declarative information, with code. After an attribute is associated with a program entity, the attribute can be queried at run time by using a technique called reflection.
Example 1 : No attribues
In this example I will create a couple of Filter classes and I will group them based on a particular category (in this case the data type).
This example will not leverage the power of class attributes, and will show how data will be defined more then once to reach the end required result.
class FilterClassic
class FilterClassicIsText
class FilterClassicIsInteger
Let’s start by defining our classes:
public abstract class FilterClassic { public abstract FilterDataType FilterDataType { get; } public abstract bool Test(object a); public FilterClassic() { } } public class FilterClassicIsInteger : FilterClassic { //The category of this filter public override FilterDataType FilterDataType => FilterDataType.Integer; //Implementation public override bool Test(object a) { try { Convert.ToInt32(a); return true; } catch { return false; } } } public class FilterClassicIsText : FilterClassic { //The category of this filter public override FilterDataType FilterDataType => FilterDataType.Text; //Implementation public override bool Test(object a) { try { Convert.ToString(a); return true; } catch { return false; } } }
Example 1 - Usage
In our solution we want to store our strategy types in a dictionary where the key would be the data type the class tests.
As you can see this categorization is defined twice (once in the class itself and again in our dictionary).
Obviously this code is duplicated and prone to human errors, especially if you have more then one filter classes defined.
//need to specify FilterDataType in the dictionary - multiple definition of .Integer static Dictionary<FilterDataType, Func> filters = new Dictionary<FilterDataType, Func> { { FilterDataType.Integer, () => new FilterClassicIsInteger() }, { FilterDataType.Text, () => new FilterClassicIsText() }, };
Example 2 : With class attribues
Defining an attribute is as simple as creating a class that inherits from System.Attribute.
Our attribute will store the FilterDataType information inside.
[AttributeUsage(AttributeTargets.Class,AllowMultiple = false, Inherited = false)] public class FilterDataTypeAttribute : Attribute { public FilterDataType FilterDataType { get; set; } public FilterDataTypeAttribute(FilterDataType filterDataType) { this.FilterDataType = filterDataType; } }
Let’s create a helper class, which will read the class attribute from a given type
public class FilterMetadata { public FilterDataType FilterDataType { get; private set; } public FilterMetadata(Type t) { FilterDataType = t.GetCustomAttribute().FilterDataType; } }
As you will see now the information about the data type is not directly embedded in the class definition but it’s a class attribute, and we will use this information both inside the class and outside:
public abstract class Filter { public abstract bool Test(object a); public FilterMetadata FilterMetadata { get; private set; } public Filter() { //Now we get the information about FilterDataType FilterMetadata = new FilterMetadata(this.GetType()); } } [FilterDataType(FilterDataType.Text)] public class FilterIsText : Filter { //Implementation public override bool Test(object a) { try { Convert.ToString(a); return true; } catch { return false; } } } [FilterDataType(FilterDataType.Integer)] public class FilterIsInteger : Filter { //Implementation public override bool Test(object a) { try { Convert.ToInt32(a); return true; } catch { return false; } } }
Example 2 - Usage
in our solution we will create a helper function that reads the DataType from the class attribute and use this information as a key for our dictionary
//Our dictionary static Dictionary<FilterDataType, Func> filtersWithMeta = new Dictionary<FilterDataType, Func>(); //the helper function static void AddFilter(Func generator) where T : Filter { filtersWithMeta.Add(new FilterMetadata(typeof(T)).FilterDataType, generator); } //we can add a new strategy like this: AddFilter<FilterIsText>(() => new FilterIsText()); //<FilterIsText> is used by the helper function to acquire the FilterDataType, but visual studio can //detect this type without specifying it, so we can also do: AddFilter(() => new FilterIsInteger()); //results {FilterDataType.Integer, () => new FilterIsInteger()}