Introduction
This tip will show you how to create dynamic instances using custom attributes with constructor values.
Background
During refactoring of the code, I came across switch
statement and one of my architects suggested that I use Strategy Pattern to refactor the code.
I did understand the Strategy Pattern and implemented the same. I was a little unhappy with the set of new objects that we were creating in the Context Class of Strategy Pattern.
Every time a new case was added, I had to create a class for the newly added type and add the dictionary value in the Context class code.
Something struck my mind and I thought of using the Reflection powered by Custom Attributes.
Although Reflection is a powerful tool, we should use it in places where it is really required. (It's like with great power comes great responsibility.)
Using the Code
Let's take a quick look at the problem at hand.
public enum ePassengerTitle
{
Mr,
Mrs,
Doctor,
}
ePassengerTitle title = ePassengerTitle.Doctor;
switch (title)
{
case ePassengerTitle.Mr:
break;
case ePassengerTitle.Mrs:
break;
case ePassengerTitle.Doctor:
break;
default:
break;
}
In the above code, there are 3 case
conditions and if the cases keep growing, the maintainability of the code takes a hit.
Solution to the Problem
There are multiple ways in which this problem could be solved. I am going to show how we use custom attributes powered with reflections.
We create different classes for all the "Case
" Statements - A little smell of Strategy Pattern.
An Interface defined.
public interface IPassengerTitleStrategy
{
void DoSomthing(string title);
}
[AutoResolve("Mr")]
public class MrPassengerTitleStrategy : IPassengerTitleStrategy
{
public void DoSomthing(string title)
{
Console.WriteLine("The Title is" + title);
}
}
[AutoResolve("Mrs")]
public class MrsPassengerTitleStrategy : IPassengerTitleStrategy
{
public void DoSomthing(string title)
{
Console.WriteLine("The Title is" + title);
}
}
[AutoResolve("Doctor")]
public class DoctorPassengerTitleStrategy : IPassengerTitleStrategy
{
public void DoSomthing(string title)
{
Console.WriteLine("The Title is" + title);
}
}
If you closely observe each class, we have a Custom Attribute. That attribute is the Key to the solution.
Let's define it now.
public class AutoResolveAttribute : Attribute
{
public AutoResolveAttribute(string name)
{
}
}
It has a constructor. It's an important aspect.
Next, we need to write Reflection logic which will find you all the classes where the attribute is decorated.
public static IEnumerable<Type> GetTypesWith<TAttribute>(bool inherit)
where TAttribute : System.Attribute
{
return from a in AppDomain.CurrentDomain.GetAssemblies()
from t in a.GetTypes()
where t.IsDefined(typeof(TAttribute), inherit)
select t;
}
So what does the above method do. It searches through all the assemblies in the AppDomain
and finds those classes that are decorated with AutoResolve
attribute. It is a generic method so you can pass any attribute of your choice.
Once we get all the classes, we need to loop through those classes to find out which class has been decorated with the string
value that we are looking for.
private static string GetAttributeName(string value)
{
var getAttribute = GetTypesWith<AutoResolveAttribute>(true);
foreach (var iAttributeValue in getAttribute)
{
var attributeValue = iAttributeValue.CustomAttributes.Select
(x => x.ConstructorArguments[0].Value).First();
if (attributeValue.ToString().Contains(value))
return iAttributeValue.FullName;
}
return string.Empty;
}
So the above method takes the string
as parameter and returns the FullName
of the assembly.
Once we get the full name of the assembly, we now need to get an instance. So we use the below code for creating an instance.
public static object GetInstance(string strFullyQualifiedName)
{
Type type = Type.GetType(strFullyQualifiedName);
if (type != null)
return Activator.CreateInstance(type);
return null;
}
So finally, what do we do now.
We use the code. That is so obvious.
ePassengerTitle title = ePassengerTitle.Doctor;
var typeCode = GetAttributeName(title.ToString());
var getInstance = GetInstance(typeCode) as IPassengerTitleStrategy;
getInstance?.DoSomthing(title.ToString());
So we get an instance of the specific switch
value and you use it. ??
Finally, what did we achieve by doing so.
Pros
- Eradicated the need to write
switch
case
- Wrote more cleaner, more maintainable and scalable code
- Code that adheres to SOLID principles
Con
- Reflection Code - A powerful tool that needs to be used with care