Microsoft & .NET.NETDumping an Object's State with Reflection and Extension Methods

Dumping an Object’s State with Reflection and Extension Methods

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Introduction

Programmers have a lot of problems to manage. Programmers have to manage their time by getting the most out of the least amount of code. Programmers also have to write code that appeals to users in terms of its responsiveness, correctness, and utility. Balancing writing reusable general-purpose code to meet deadlines and budgets with the end users’ needs is part of what makes software development challenging.

Knowing how to balance competing tasks is an important skill. Having many tools in your arsenal is the key to success. Look at one side of the coin, writing a general-purpose algorithm for dumping the state of any collection of any objects. You’ll use extension methods, a new feature in .NET, to get the job done.

Defining a Test Class

Code that is used often can be dragged into the Toolbox. I use the Customer class for so many demos that it is one of those things. For your purposes, you could use any class, but I have used a simple entity class from the Customers table in the Northwind database (see Listing 1).

Listing 1: A sample customer class.

Public Class Customer

   ''' <summary>
   ''' Initializes a new instance of the Customer class.
   ''' </summary>
   ''' <param name="customerID"></param>
   Public Sub New(ByVal customerID As String)
      FCustomerID   = customerID
      FCompanyName  = "Company " & customerID.ToString()
      FContactName  = "George Contact"
      FContactTitle = "Dr."
   End Sub

   Private FCustomerID As String = ""
   Public Property CustomerID() As String
      Get
         Return FCustomerID
      End Get
      Set(ByVal Value As String)
         FCustomerID = Value
      End Set
   End Property

   Private FCompanyName As String
   Public Property CompanyName() As String
      Get
         Return FCompanyName
      End Get
      Set(ByVal Value As String)
         FCompanyName = Value
      End Set
   End Property

   Private FContactName As String = ""
   Public Property ContactName() As String
      Get
         Return FContactName
      End Get
      Set(ByVal Value As String)
         FContactName = Value
      End Set
   End Property

   Private FContactTitle As String = ""
   Public Property ContactTitle() As String
      Get
         Return FContactTitle
      End Get
      Set(ByVal Value As String)
         FContactTitle = Value
      End Set
   End Property

   Private FAddress As String = ""
   Public Property Address() As String
      Get
         Return FAddress
      End Get
      Set(ByVal Value As String)
         FAddress = Value
      End Set
   End Property

   Private FCity As String = ""
   Public Property City() As String
      Get
         Return FCity
      End Get
      Set(ByVal Value As String)
         FCity = Value
      End Set
   End Property

   Private FRegion As String = ""
   Public Property Region() As String
      Get
         Return FRegion
      End Get
      Set(ByVal Value As String)
         FRegion = Value
      End Set
   End Property

   Private FPostalCode As String = ""
   Public Property PostalCode() As String
      Get
         Return FPostalCode
      End Get
      Set(ByVal Value As String)
         FPostalCode = Value
      End Set
   End Property

   Private FCountry As String = ""
   Public  Property Country() As String
      Get
         Return FCountry
      End Get
      Set(ByVal Value As String)
         FCountry = Value
      End Set
   End Property

   Private FPhone As String = ""
   Public Property Phone() As String
      Get
         Return FPhone
      End Get
      Set(ByVal Value As String)
         FPhone = Value
      End Set
   End Property

   Private FFax As String = ""
   Public Property Fax() As String
      Get
         Return FFax
      End Get
      Set(ByVal Value As String)
         FFax = Value
      End Set
   End Property
End Class

Defining an Object State Dumper with Extension Methods

A common theme in programming, especially during debugging, is to examine the state of objects. A good first attempt is to override the ToString method and manually write each property. This works, of course, but the problem is that one must do this for every class. Ultimately, every programmer realizes that overriding ToString and writing long property-state statements is time consuming and must be changed each time the class changes.

After a little time and pain, most programmers stumble upon reflection and write a more general-purpose algorithm that will dump the state of any object. There are several variations on this theme. Accept an object, reflect its properties, and write them to the Console, the Debug window, or a StringBuilder. Listing 2 has the variation I created that I like the best. Listing 2 accepts and uses extension methods, extends object—so any type can call Dump—and accepts a TextWriter. Accepting a TextWriter means that the caller can pass in the dump-target. For example, Console.Out is a TextWriter; passing in Console.Out means that the output goes to the Console.

Listing 2: Extension methods that dump the state of a collection of objects.

Imports System.Runtime.CompilerServices
Imports System.Reflection
Imports System.IO

Public Module Dumper

   <Extension()> _
   Public Sub Dump(ByVal obj As Object, ByVal writer As TextWriter)
      Dim properties As PropertyInfo() = _
         obj.GetType().GetProperties()
      For Each p As PropertyInfo In properties
         Try
            writer.WriteLine(String.Format("{0}: {1}", p.Name, _
            p.GetValue(obj, Nothing)))
         Catch
            writer.WriteLine(String.Format("{0}: {1}", p.Name, _
            "unk."))
         End Try
      Next
   End Sub

   <Extension()> _
   Public Sub Dump(Of T)(ByVal list As IEnumerable(Of T), _
      ByVal writer As TextWriter)
      For Each o As T In list
         o.Dump(writer)
      Next
   End Sub
End Module

Adding the ExtensionAttribute to a method means that for all intents and purposes the extension method is called like a member method. For example, customer.Dump(Console.Out) would call the first Dump method even though Dump isn’t actually a member of the Customer class. With extension methods, the first argument defines the type being extended, so defining Dump as accepting an object means Dump extends everything.

The overloaded Dump extends IEnumerable(Of T), which means CustomerList.Dump(Console.Out)—assuming CustomerList is defined as List(Of Customer)—would call the second Dump method. The second Dump method iterates over every object and calls Dump on the object.

Tip: Add a Log property defined as a TextWriter type to all of your classes; that makes it easy to add trace-ability to any TextWriter target at any point during development.

The first Dump method simply uses reflection to get all of the public properties and writes the property name and value to the TextWriter argument.

Testing the Object State Dumper

The code in Listing 3 creates a List of Customer objects. A stopwatch is used to see how long it takes to dump the list of Customers. Look at the code and then I’ll talk about the purpose for the stopwatch.

Listing 3: Code to test the object dumper.

Imports System.Reflection
Imports System.IO
Imports System.Runtime.CompilerServices
Imports System.Reflection.Emit

Module Module1

   Sub Main()

      ' Create customer list
      Dim customers As List(Of Customer) = New List(Of Customer)
      For i As Integer = 0 To 50
         customers.Add(New Customer(i.ToString()))
      Next

      ' Dump customer list with stopwatch
      Dim watch As Stopwatch = Stopwatch.StartNew
      customers.Dump(Console.Out)
      Console.WriteLine(watch.Elapsed)
      Console.ReadLine()

   End Sub

End Module

Notice how Dump is used: customers.Dump(Console.Out). The first argument is supplied by the member-of semantics. The object—a collection of Customer objects—is actually the first argument to Dump(ByVal list As IEnumerable(Of T), ByVal writer as TextWriter), and Console.Out is the TextWriter argument.

The purpose of the StopWatch is to show you how long the code takes to run. The reason is to illustrate that the benefit to the reflection algorithm is utility for the programmer and to limit the amount of labor involved in dumping object states. The problem is that what is simple for programmers is often not performant for end users. Unless you were dumping object states in user code, this algorithm—Dump—is suitable. If the end user is going to experience the sluggish performance of reflection, you may want to solve the problem a different way.

Summary

A common problem is dumping object state for debugging purposes without writing a lot of code. The dumper in this article uses extension methods, reflection, overloading, and generics to facilitate sending an object’s state to any TextWriter, which includes Console.Out.

The key to being a great programmer is to be able to invent solutions like this one and to know when it is suitable to use. Reflection is great, especially for a write-once general purpose programmer utility, but if the end user is going to experience the sluggish performance of Reflection, it might be nice to have alternatives handy. Come back next week and I will show you one.

About the Author

Paul Kimmel is the VB Today columnist for www.codeguru.com and has written several books on object-oriented programming and .NET. Check out his upcoming book LINQ Unleashed for C# now available on Amazon.com and fine bookstores everywhere. Look for his upcoming book Teach Yourself the ADO.NET Entity Framework in 24 Hours. You may contact him for technology questions at pkimmel@softconcepts.com.

Copyright © 2008 by Paul Kimmel. All Rights Reserved.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories