Using Reflection to Dump Objects

Environment: Internet, Network, VC++

How to Use Reflection to Dump Objects

In this article, I will show how to write some .Net code to use a feature called "reflection" to generate a String containing values from all object fields. Please feel free to e-mail me for any comment or question regarding any detail or explanation about this source code.


First of all, what is reflection? Well, it is a set of classes defined in the namespace System.Reflection. With these classes (and mainly with static methods), at runtime, you can inspect any object without knowing its type and discover its methods, fields, properties, and constructors. Then, you can invoke these methods, read/write property (or field) values or create another object like the source one invoking the constructors.

Basically, if you use the debug features in Visual Studio .Net, you have already seen one example of reflection usage. The development environment uses reflection in the watch and auto windows when you try to display something more complex than an integer or a simple value type. When you expand the plus to the left of such a variable, reflection is used to retrieve fields and properties names and values.

What we are writing is a simple class named Dumper that will work in a very similar way. This is only a little sample of what is possible when you use reflection. Try to think about the other great potentials (and also to the security risks) of accessing every private field in any class not written by you (adding buttons to toolbars, changing forms layout, invoking private methods, and so on).

The following image shows the demo project output. As you can see, it is simpler than what you can see in Visual Studio, but it is quite useful if you think to implement some kind of logging in your applications. Simply, you can think to trap exceptions, display a generic error message to users, and then save detailed object data in some log file.

External Visible Interfaces

The Dumper class is the typical utility class. So, every method must be declared as static. This class must never be instantiated. For this motivation, we declare a protected empty constructor. If we don't provide a constructor, the compiler automatically adds an empty constructor invoking the Object constructor. We choose the protected modifier because we want to allow someone to inherit from our class. If the constructor is private, the class is not inheritable because there is no way for the child object to construct its father.

We provide dump features through an overloaded method called DumpObject. Mainly, it would be enough to pass it the object to dump but, seeing that an object can contain many other big objects hosting other objects and so on, it is better to also provide another version of this method where we can specify the maximum allowed nesting level.

Then, we will write some other methods basically used internally. The following image shows the Dumper class.

How the Dumper Works

Our starting point would be a class like the following one (Table 1):

 1: using System;
 2: using
System.Text;
 3: using
System.Reflection; 

 4: namespace DumperLib
 5: {
 6:       public class Dumper
 7:       {
 8:             protected Dumper()
 9:             {
10:             } 

11:             public static string DumpObject(object obj)
12:             {
13:                   return Dumper.DumpObject(obj, -1);
14:             }

15:             public static string DumpObject(object obj, int MaxLevel)
16:             {
17:                   /// We add code here.
18:             }

19:       }
20: }

Table 1

As you can see, the DumpObject version with no maximum allowed nesting level will simply call the other version with a special parameter value (-1). The other version will do the following thing:

  1. It instantiates a StringBuilder object used to host the partially generated dump (rows 1 and 2). This is a better solution than using a String object because every time you append something to a String, a new String object is generated and the reference to the previous object is lost. This is a memory waste.
  2. Check whether the object to dump is a valid reference (rows 3-9)
  3. Call an internal function to physically dump the object (row 7).

Everything is summarized in the following code (Table 2):

 1: StringBuilder sb;

 2: sb = new StringBuilder(10000);

 3: if (obj == null)
 4:  return "Nothing";
 5: else
 6:  {
 7:   Dumper.PrivDump(sb, obj, "[ObjectToDump]", 0, MaxLevel);
 8:   return sb.ToString();
 9:  }

Table 2

The PrivDump function is shown next (Table 3):

 1: protected static void PrivDump(StringBuilder sb, object obj,
 2:           string
objName, int level, int MaxLevel)
 3: {
 4:       if (obj == null)
 5:             return;
 6:       if (MaxLevel >= 0 && level >= MaxLevel)
 7:             return;

 8:       string padstr;
 9:       padstr = "";

10:       for(int i=0;i<level;i++)
11:             if (i<level-1)
12:                   padstr+="|";
13             else
14:
                  padstr+="+";

15:       string str;
16:       string[] strarr;
17:       Type t;

18:       t = obj.GetType();
19:       strarr = new String[7];
20:       strarr[0] = padstr;
21:       strarr[1] = objName;
22:       strarr[2] = " AS ";
23:       strarr[3] = t.FullName;
24:       strarr[4] = " = ";
25:       strarr[5] = obj.ToString();
26:       strarr[6] = "\r\n";

27:       sb.Append(String.Concat(strarr));

28:       if (obj.GetType().BaseType == typeof(ValueType))
29:             return;

30:       Dumper.DumpType(padstr, sb, obj, level, t, MaxLevel);

31:       Type bt;
32:       bt = t.BaseType;
33:       if (bt != null)
34:       {
35:             while (!(bt == typeof(Object)))
36:             {
37:                   str = bt.FullName;
38:                   sb.Append(padstr + "(" + str + ")\r\n");
39:                   Dumper.DumpType(padstr, sb, obj, level, bt, MaxLevel);
40:                   bt = bt.BaseType;
41:                   if (bt != null)
42:                         continue;
43:                   break;
44:             }
45:       }
46: }

Table 3

Let's try to look at the code. Obviously, the algorithm must be recursive, so we have to insert a recursion termination check. If the object is null or if we have reached the maximum nesting level, we have to exit immediately (rows 4-7).

Then, we generate a padding string composed by many pipe symbols as the reached nesting level less one and a plus symbol (rows 8-14). This is done to build a string similar to the Visual Studio .Net tree structure. We append to the StringBuilder (rows 15-27) something composed by this padding string, the field name, the type name, and the string representation of the field (obtained calling the ToString method).

Here, we have to add another termination check. If we have a field that is a value type (in this simple class, we don't care about fields that are structs), we already have fully dumped it, so we can exit from the function (rows 28-29). In all the other cases, we have to call another internal function named DumpType. This function will retrieve every field of this object instance to make their dump (row 30).

Now, we have to print every field value for this object but from the parent class point of view. So (in rows 31-46) we obtain the base class type; if it is valid we call DumpType, passing it the same object but the parent class type.

Finally, there is the DumpType method (See Table 4).

 1: protected static void DumpType(string InitialStr, StringBuilder sb,
 2:        object
obj , int level, System.Type t, int maxlevel)
 3: {

 4:       if (t == typeof(System.Delegate)) return;

 5:       FieldInfo[] fi;
 6:       fi = t.GetFields(BindingFlags.Public | BindingFlags.NonPublic |
 7:           BindingFlags.Instance);

 8:       foreach( FieldInfo f in fi)
 9:             PrivDump(sb, f.GetValue(obj), f.Name, level + 1, maxlevel);

10:       object[] arl;
11:       int i;

12:       if (obj is System.Array )
13:       {
14:             try
15:
            {
16:                   arl = (object[])obj;
17:                   for (i = 0;i<arl.GetLength(0);i++)
18:                         PrivDump(sb, arl[i], "[" + i + "]", level + 1, maxlevel);
19:             }
20:             catch (Exception){}
21:       }
22: }

Table 4

This is our class core method. The first thing we have to test is whether this field is a delegate (row 4). A delegate is a typed function pointer. Obviously, we cannot obtain a delegate value, so we have to exit immediately.

In all the other cases, we obtain fields' information by invoking the GetFields method from the type. This method returns a FieldInfo array (rows 5-7). Here, we specify that we want to retrieve every field (public, protected, private, and static).

To dump the field values (rows 8-9), we can call the PrivDump method again passing to it the field value obtained by invoking the GetValue method from a FieldInfo reference.

If the object to dump is an array (rows 10-22), we have to dump every array cell by calling the PrivDump method.

How to Test Our Work

The code to test the class is very simple. You have to call the DumpObject method by passing a valid object as in the following code fragment (Table 5).

 1: Console.WriteLine(DumperLib.Dumper.DumpObject(new Form(),5));

Table 5

Downloads

Download source code and demo project - 12 Kb


Comments

  • Memory waste?

    Posted by Legacy on 05/06/2003 12:00am

    Originally posted by: David P

    Sorry, it seems funny that you should use StringBuider to avoid the memory waste of using Strings, but then initialize your StringBuilder to a size of 10KB.

    This is a good example for using reflection, but I think a couple of things are worth mentioning.

    I haven't tried this code but isn't it true that if I called DumpObject(LL) for a doubly linked list "LL" with two elements, the function would recurse forever and cause stack overflow?

    We also call toString() on the object itself, which is potentially dangerous. Example: suppose your program knows it is crashing, so it wants to dump some debug info on exit. So it calls DumpObject on some of the program's objects. Suppose one of those objects is in an invalid state... then toString() might crash, so the debug dump would fail.

    Reply
  • Can this do a lil more ?

    Posted by Legacy on 05/05/2003 12:00am

    Originally posted by: Enkay

    I really liked the article. I have a Q though. I am trying to open a file persisted using MFC CArchive class which contains a tree structure. Is it possible to use/modify your code and retrive the persisted object ?

    Thanx!

    Reply
  • Superb!

    Posted by Legacy on 05/01/2003 12:00am

    Originally posted by: Shahin Jahromi

    .net PageSetup dialog is doing a bad job returning PaperSize class for Custom Sizes.This dumper helped me out BIG TIME ( to access the private fields of PageSettings instead of reading the public properties )! Thanks a lot!

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: November 20, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT Are you wanting to target two or more platforms such as iOS, Android, and/or Windows? You are not alone. 90% of enterprises today are targeting two or more platforms. Attend this eSeminar to discover how mobile app developers can rely on one IDE to create applications across platforms and approaches (web, native, and/or hybrid), saving time, money, and effort and introducing apps to market faster. You'll learn the trade-offs for gaining long …

  • IBM Worklight is a mobile application development platform that lets you extend your business to mobile devices. It is designed to provide an open, comprehensive platform to build, run and manage HTML5, hybrid and native mobile apps.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds