Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Creating Dynamic Instances Using Custom Attributes with Constructor Values

0.00/5 (No votes)
24 Nov 2016 2  
How to create dynamic instances using custom attributes with constructor values

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:
                // do something
                break;
            case ePassengerTitle.Mrs:
                // do something
                break;
            case ePassengerTitle.Doctor:
                // do something
                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

  1. Eradicated the need to write switch case
  2. Wrote more cleaner, more maintainable and scalable code
  3. Code that adheres to SOLID principles

Con

  1. Reflection Code - A powerful tool that needs to be used with care

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here