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

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read