Implementing Self-Reflection with Extension Methods

Tuesday Apr 20th 2010 by Paul Kimmel
Share:

Write a reflection mechanism one time to reflect any object or collection; knowing your code's state is the first step in writing bug-free code.

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

In VB.NET the easiest way to implement an extension method is to add a module to your project, declare one or more public sub routines or functions and add the Extension attribute. Here is part of the solution that demonstrates the basic syntax:

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.

Share:
Home
Mobile Site | Full Site
Copyright 2017 © QuinStreet Inc. All Rights Reserved