Implementing Self-Reflection with Extension Methods
Introduction
One of the books that was instrumental for me in writing bug free code was Dave Thielen's No Bugs! Dave Thielen was a key factor in writing the well-received MSDOS 5.0. (It was a long time ago.) After buggy MSDOS 4.0 everyone was ready for a less buggy version of DOS. Dave's No Bugs! Talked about tracing, trapping, and asserting long before those capabilities were built into the framework, long before there really was a framework--least a great one.
Now assertions and tracing are part of the .NET framework and have been all along. The ability to closely examine an object's state has been around for a while now too. Dumping your object's state was critical in checking for null pointers, especially in C++ programming. With the System.Reflection namespace and extension methods you can write a dozen or so lines of code that will use reflection to display any object or collection of object's state information. This article demonstrates how and along the way you will see a little LINQ and the DirectCast method.
Implementing Extension Methods in VB.NET
Public Module SelfReflect <Extension()> Public Sub Reflect(ByVal O As Object) Reflect(O, Console.Out) End Sub End Module
A module is basically the same as C# progammings's static class. The ExtensionAttribute
tells the compiler that the method is an extension method. That is, it will be used with member calling syntax object.method()
. The first argument defines the type being extended. Again, in this example, it means that for all intents and purposes Reflect can be treated as a member method of the Object type. Since Object is the base class for all .NET classes they are treated as a member of every class even those you define.
Overloading Extension Methods
Like all methods extension methods can be overloaded. Overloading means to define a method with the same name as another method with a different parameter signature. Overloading solved the problem of needing to define and remember different method names based on parameter types, such as PrintInt, PrintString, PrintObject, etc. Having to define potentially dozens of differently named methods based on parameter type would make using a framework much more challenging. Overloading lets the compiler call the right method by examining the parameter signature. (Overloaded methods are actually uniquely named internally based on a concept called name mangling.)
To continue our reflection solution we now can add a second Reflect method that extends Object and accepts a TextWriter second parameter. Listing 1 shows the SelfReflect module with the overloaded Reflect method.
Imports System.Runtime.CompilerServices Imports System.IO Imports System.Reflection Imports System.Collections Public Module SelfReflect <Extension()> Public Sub Reflect(ByVal O As Object) Reflect(O, Console.Out) End Sub <Extension()> Public Sub Reflect(ByVal O As Object, ByVal writer As TextWriter) If (TypeOf O Is IEnumerable) Then DirectCast(O, IEnumerable).Reflect(writer) Dim info As PropertyInfo() = O.GetType().GetProperties() For Each item As PropertyInfo In info Try writer.WriteLine("{0} : {1}", item.Name, item.GetValue(O, Nothing)) Catch writer.WriteLine("{0} : {1}", item.Name, item.ToString()) End Try Next writer.WriteLine(O) End Sub End Module
Listing 1: SelfReflect with two Reflect methods.
The second Reflect extension method accepts a TextWriter (which is what Console.Out is an instance of). The second overloaded method checks to see if the object argument is IEnumerable; if it is a third version of Reflect that is called by using DirectCast to cast the Object parameter to IEnumerable. If not System.Reflection
is used to get the property information and dump the properties to the TextWriter object. Listing 2 contains the complete implementation of the SelfReflect class.
Imports System.Runtime.CompilerServices Imports System.IO Imports System.Reflection Imports System.Collections Public Module SelfReflect <EXTENSION()> Public Sub Reflect(ByVal O As Object) Reflect(O, Console.Out) End Sub <EXTENSION()> Public Sub Reflect(ByVal O As Object, ByVal writer As TextWriter) If (TypeOf O Is IEnumerable) Then DirectCast(O, IEnumerable).Reflect(writer) Dim info As PropertyInfo() = O.GetType().GetProperties() For Each item As PropertyInfo In info Try writer.WriteLine("{0} : {1}", item.Name, item.GetValue(O, Nothing)) Catch writer.WriteLine("{0} : {1}", item.Name, item.ToString()) End Try Next writer.WriteLine(O) End Sub <EXTENSION()> Public Sub Reflect(ByVal O As IList, ByVal writer As TextWriter) For Each elem In O Reflect(elem, writer) Next End Sub <EXTENSION()> Public Sub Reflect(ByVal O As IEnumerable, ByVal writer As TextWriter) For Each elem In O Reflect(elem, writer) Next End Sub End Module
Listing 2: The complete implementation of the SelfReflect class.
The final two overloaded versions of Reflect are extension methods that iterate over the items in an IList or IEnumerable type. To test SelfReflect create some various kinds of objects and use the member of operator (,) to invoke Reflect. Listing 3 contains a Main console method that demonstrates.
Imports System.Runtime.CompilerServices Imports System.IO Imports System.Reflection Imports System.Collections Module Module1 Sub Main() Dim str As String = "Hello World!" str.Reflect() Dim strings = {"Hello", "World!"} strings.Reflect() Dim chars = From ch In str.Split() Select ch chars.Reflect() Console.ReadLine() End Sub End Module
Listing 3: Testing SelfReflect
Main tests Reflect on a String, an array of strings, and the results from a LINQ query. All of the properties of each type, or each element of each type are properly displayed. (Since IList is IEnumerable you can leave that version of the Reflect method off.)
Summary
Adding code to help you debug has a special name now; it is referred to as instrumentation. The Reflect method and capability is part of that notion. By writing code that lets you immediately access an object's current state you can easily see if a particular object is not in an expected state. Combine this with Assert and Trace and you have taken several long strides forward in writing bug free code.
In this article you got to see the less cumbersome DirectCast method, how to implement extension methods, how to overload methods, and how to use Reflection. These are all valuable skills in the .NET developer lexicon.
Comments
Interesting
Posted by kevininstructor on 04/21/2010 09:58amyou CAN create extension methods in VS 2008
Posted by DEA_I on 04/27/2010 03:25amKevin, I already used extension methods with VS 2008 and all the options on. What was your problem? Instead of your first extension take a look at Int32.Parse or even Int32.TryParse. I like the thing with the quotes - nice idea! Darius
ReplyYes, in VS2008
Posted by pkimmel on 04/21/2010 09:25amAll articles will probably be in VS2010 and that one was--the RC version. I must not have mentioned it. Sorry.
Replyjust for the record, one must escape as > and <
Posted by mkamoski on 04/21/2010 09:20amug one more time, just because
Posted by mkamoski on 04/21/2010 09:19amGreat article, minor code tweak possibly needed
Posted by mkamoski on 04/21/2010 09:15amThis...
...probably should be this...
_
...with the line continuation character.
That is minor-- so, this is a REALLY great article.
Thank you.
-- Mark Kamoski
let's try that comment again with source code this time
Posted by mkamoski on 04/21/2010 09:17am