The Power of Reflection

I just realized this morning a lot of my articles talk about how these new code constructs replace the majority of the manual reflection we used to have to do, when I haven’t really talked about Reflection itself! So here’s a quick summary.

Reflection is the ability of a programming language to look at it’s own compiled code programmatically. Using the information you can derive from a compiled library, you can instantiate and execute code from that library. This is mostly done using the Type and Assembly classes. Most people would think, “Well, if you have the library already, why don’t you just reference it an use it that way?” Yeah, I know, it doesn’t sound entirely useful right off the bat, but there’s a number of scenarios where it does come in handy. I’ll list a few examples that we use in our own code.

IQ.Framework

Our internal library uses reflection to instantiate services running locally in debug mode. Here’s a summary (names changed to protect the innocent)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public T CreateService<T>()
{
  Assembly debugAssembly = Assembly.LoadFrom(fileName);
  foreach (Type exportedType in debugAssembly.GetExportedTypes())
  {
      if (typeof(IApplicationService).IsAssignableFrom(exportedType))
      {
          var serviceType = exportedType;
          break;
      }
  }
  if (serviceType != null)
  {
      return (T)Activator.CreateInstance(serviceType);
  }
}

On the other side of the fence, we use attributes to generate service endpoints

1
2
3
4
5
6
7
8
Type[] interfaceTypes = serviceType.GetInterfaces();
foreach (Type interfaceType in interfaceTypes)
{
  if (Attribute.IsDefined(interfaceType, typeof(ServiceContractAttribute)))
  {
      //create endpoint based on attribute data
  }
}

DevBoard

DevBoard is an internal dashboard application that uses reflection to go through each assembly loaded in a specific folder, looking for classes that implement IChannel or ITicker (interfaces I defined). It then uses that list to dynamically instantiate and run the Watch/Update method on each class, which generates the content that we see on the screen.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//part of default.aspx.cs
DirectoryInfo di = new DirectoryInfo(Server.MapPath("~/channels"));
foreach (FileInfo fi in di.GetFiles("*.dll"))
{
  Assembly a = Assembly.LoadFile(fi.FullName);
  foreach (Type t in a.GetTypes())
  {
      if (t.GetInterface("DevBoardModule.IChannel") != null)
      {
          //see if channel already exists
          if (modules.Select("AssemblyPath = '" + fi.FullName + "' AND Class = '" + t.FullName +
              "' AND Type = 0").Length == 0)
          {
              //add channel module to list
              modules.Rows.Add(Guid.NewGuid(), fi.FullName, t.FullName,
                  (int)DevBoardModule.ModuleTypes.Channel);
          }
      }
      else if (t.GetInterface("DevBoardModule.ITicker") != null)
      {
          if (modules.Select("AssemblyPath = '" + fi.FullName + "' AND Class = '" + t.FullName +
              "' AND Type = 1").Length == 0)
          {
              modules.Rows.Add(Guid.NewGuid(), fi.FullName, t.FullName,
                  (int)DevBoardModule.ModuleTypes.Ticker);
          }
      }
  }
}
//DevBoardTicker.ascx
DataTable modules = ((_Default)Page).modules;
DataRow ticker = modules.Select("ModuleID = '" + Request.Form["id"] + "'")[0];
ITicker tick = ((_Default)Page).GetTickerByID((Guid)ticker["ModuleID"]);
Response.Write(string.Format("{0}: {1} - ", tick.Name, tick.Update()));

iQcommerce

The way iQcommerce populates clients’ web pages with data uses Reflection. We parse the HTML, looking for specific attributes that specify classes in our Item Library Assembly, and then use those classes to generate the HTML that is displayed to a customer.

1
2
3
4
5
6
7
8
9
10
11
//Load item assembly
Assembly a = Assembly.GetExecutingAssembly();
XPathNodeIterator xpNI = xpNav.Select("//*[@iqitem][not(ancestor::*[@iqitem])]");
while (xpNI.MoveNext())
{
    Type t = a.GetType(Service.ItemAssemblyName + "." + iQciDR.ClassName);
    if (t != null)
    {
        iQcItem i = (iQcItem)Activator.CreateInstance(t);
    }
}

Surface Kiosk

We use Reflection in our CacheProxy class to determine if a method the application is calling should have its results cached. This is done by checking if the method has a CacheAttribute defined on the interface’s method definition.

1
2
3
4
5
6
7
8
9
10
11
public override IMessage Invoke(IMessage msg)
{
  if (!(msg is IMethodCallMessage))
      return null;
  IMethodCallMessage methodCallMessage = (IMethodCallMessage)msg;
  if (!Attribute.IsDefined(methodCallMessage.MethodBase, typeof(CacheableAttribute)))
  {
      //attribute not found, execute method normally
  }
  //else, look in the cache and/or save the method results to the cache
}

So, in summary, you can see a number of situations where Reflection is useful, and it’s a powerful feature of any current programming language. New constructs in .NET 4.0 like the “dynamic” keyboard (or any of the new Dynamic Language Runtime for that matter) wrap this already-existing functionality to make it even easier to use Reflection in your programming tasks.

Comments