Friday, March 13, 2009

.Net Reflection, hey look I can see myself!

Towards the end of Week 1 in the Master's Program, we touched on Reflection and were shown how they can be used with attributes. Here's a quick review. Let's decorate our Person class we keep using with a Description attribute.

   63     [Description("This represents a Person's basic information.")]
   64     public class Person
   65     {
   66         public string FirstName { get; set; }
   67         public string LastName { get; set; }
   68         public int Age { get; set; }
   69     }

What we were taught that first week was how to get the information from an attribute at runtime. Here's a simple way of doing it. In fact, just to make it a little more interesting, let's actually make this an extension method on object, so that any time an object has a Description attribute, we could simple do this:

   57 string description = myObject.GetDescription();

Here's how we can do it:

   72     public static class ObjectExtensions
   73     {
   74         public static string GetDescription(this object o)
   75         {
   76             Type type = o.GetType();
   77             object[] attributes = type.GetCustomAttributes(typeof(DescriptionAttribute), false);
   78             if (attributes.Length == 0)
   79             {
   80                 return String.Empty; //no Description attribute found
   81             }
   82 
   83             DescriptionAttribute description = (DescriptionAttribute)attributes[0];
   84             return description.Description;
   85         }
   86     }

The Type class is what really gets us started. Every object in the .NET Framework has a method called GetType() which returns a Type object. You can also get the type by using the typeof keyword to get you a speficic type. IE: If you want the "Type" of our Person object, you would do:

   58 Type personType = typeof(Person);

So if you have an object, you can call GetType() however if you don't actually have an object, but you need to get information about a sepcific Type (class), you use the typeof keyword. So in our example, we first get the Type from the object passed in, and then we use the methods on the Type class to retrieve the attributes. This is the tip of the iceberg for Reflection. Reflection is unique in the sense that you're actually examining the type of a specific class at runtime. Generally when dealing with an object, you get the values of a specific object, such as the Person's Name, or a Car's Color etc.. Reflection on the other hand tells us about the class (type) itself.

Getting attributes is just the beginning. You can do alot of really cool things with Reflection, and I'll try to demonstrate a few of them.

Using Reflection, you can get all the members of a class. You can get the Properties, Members, Methods etc. If you remember, during the first project at SetFocus, we had a requirement like this:

Provide a named enumerator called PropertyAndValuesCollection that allows for easy enumeration over a set of string values that provide information about the current Supplier object. The string values returned by the enumerator should take the user over all of the property values of the current Supplier object in the output format of: PropertyName: PropertyValue.
We had to have a method that returned all the Properties and their values of a Supplier object. So, not knowing much about reflection, most of us did something like this:

  182         public  System.Collections.IEnumerable PropertyAndValuesCollection()
  183         {
  184             yield return "ID: " + ID;
  185             yield return "CompanyName: " + CompanyName;
  186             yield return "ContactName: " + ContactName;
  187             yield return "ContactTitle: " + ContactTitle;
  188             yield return "Address: " + Address;
  189             yield return "City: " + City;
  190             yield return "Region: " + Region;
  191             yield return "PostalCode: " + PostalCode;
  192             yield return "Country: " + Country;
  193             yield return "Phone: " + Phone;
  194             yield return "Fax: " + Fax;
  195             yield return "HomePage: " + HomePage;
  196             yield return "Type: " + Type.ToString();
  197         }

This worked, and the TestHarness considered this to be OK. However, hardcoding anything sucks! What if you add/remove properties of this supplier class, now you have to remember to go change this method. Wouldn't it be better if we can just examine the class at runtime and return all the properties? Well, here's how you can do it using Reflection:
  183  public IEnumerable PropertyAndValuesCollection()
  184         {
  185             Type type = this.GetType();
  186             PropertyInfo[] infos = type.GetProperties();
  187             for (int i = 0; i<infos.Length;i++)
  188             {
  189                 yield return String.Format("{0}: {1}", infos[i].Name,
infos[i].GetValue(this, null));
  190             }
  191         }

I tested this with the TestHarness and it does in fact pass. What's happening here is very simple. First we get the Type of this class (Supplier). We could also have done Type type = typeof(Supplier). Same thing, doesn't matter. Then, we call the GetProperties method on the type object that returns an array of PropertyInfo objects. The PropertyInfo class has a property on it called Name which is just that, the name of the actual property. Then, this is where reflection really shines, it has a method called GetValue. This method enables us to actually get the value of an object at runtime!

Here's another demonstration of when this can be useful. Say you want to add a ToXml() method to your class that writes out all the values of your class to an XML string. (Yes, I know there are already classes in the .NET Framework that do this, but even wonder how they do it? :) ) Here's a simple code sample. We'll add this to our Person class:

   72 [Description("This represents a Person's basic information.")]
   73     public class Person
   74     {
   75         public string FirstName { get; set; }
   76         public string LastName { get; set; }
   77         public int Age { get; set; }
   78 
   79         public string ToXml()
   80         {
   81             Type type = this.GetType();
   82             StringBuilder builder = new StringBuilder();
   83             StringWriter stringWriter = new StringWriter(builder);
   84             XmlTextWriter writer = new XmlTextWriter(stringWriter);
   85             writer.WriteStartDocument();
   86             writer.WriteStartElement(type.Name);
   87             foreach (var prop in type.GetProperties())
   88             {
   89                 writer.WriteAttributeString(prop.Name,
prop.GetValue(this, null).ToString());
   90             }
   91             writer.WriteEndElement();
   92             return builder.ToString();
   93         }
   94     }

Now let's say we have a Person object like this:

   25             Person p = new Person
   26             {
   27                 FirstName = "Alex",
   28                 LastName = "Friedman",
   29                 Age = 27
   30             };

Now, let's call our ToXML method:

   32 string xml = p.ToXml();

This would be the result:

   37 <?xml version="1.0" encoding="utf-16"?><Person FirstName="Alex" LastName="Friedman" Age="27" />


Not very pretty, but you get the point!

In future posts, I'll dig deeper into Reflection and show some even more crazy stuff you can do like loading Assemblies at runtime and invoking methods on them.

No comments: