Поделиться через


Quick Aside on C# - Reflection’s Power and Pitfalls

I am currently developing a couple of internal test tools to help with test case development and test case data analysis. I have designed the tool to be generic, meaning that anybody that can create and serialize objects for me I can edit. Here are some of the interesting issues I’ve run into so far…

C# Reflection Tip 1:  Reflecting Over Enumerable Properties

Suppose someone gives me this object to reflect through:      

 public class Demo
{
    public int Number = 1;
    public Foo[] FooArray
    {
        get;
        set;
    }
}

I want to loop through and display all of the objects contained within the nested array. If you take the standard approach of reflecting through the Demo object’s types and collecting those types’ properties you’ll see something interesting. On the first pass of displaying the object, the type property for the Foo[] array is the strongly typed array object. Everything is fine when looping through the strongly typed elements of the array. 

Example:

 Type objType = testObject.GetType();
List<PropertyInfo> objProperties = new List<PropertyInfo>(objType.GetProperties());
          
foreach (PropertyInfo property in objProperties)
 {
 …

But, if the original object has been modified, even with data binding, it seems that there is a high probability that following the same method yields different results. Instead of the type property of the Foo[] array being strongly typed, you’ll see all of the weakly typed Array properties like SyncRoot, LongLength, IsSynchronized, etc. Now instead of displaying a clean list of Foo objects, I instead was displaying the base Array properties in the tool.

The way to fix this is by sort of 'preempting’ the fact that this is an array or enumerable collection. If I could recognize that this was an enumerable object, I would skip right ahead to looping through the nested objects like so:

 foreach (Object obj in (item.GenericObject as IEnumerable))
 Now, I can follow the same reflection probing patterns to show all of the nested object.

Visual Studio makes this issue hard to track down. While debugging, I put watches on the objects that I was expecting to loop through. The problem is that Visual Studio is too smart some times. What the debugger shows is the strongly typed object, even though its ethereal/intermediate form might be recognized by the runtime as a weakly typed generic array. 

C# Reflection Tip 2:  Don’t Forget That Strings Are Enumerable

Once I solved my generic array problem, I found that there were instances where I was displaying lots of garbage UI data that looked like it was coming from an array or collection. Again, after further investigation I found out that strings were tripping up my IsEnumerable alarm.

Here’s my check for determining if an object’s inherited interface contains IEnumerable as well as screening out strings:

 Type[] interfaces = testObject.GetType().GetInterfaces();
foreach (Type i in interfaces)
{
        if (i.IsGenericType && i.GetGenericTypeDefinition().Equals(typeof(IEnumerable<>)) && i != typeof(string))
        {
              return true;
        }
}

This can probably be done better, but it works for now.

C# Reflection Tip 3:  Use The Visual Studio Debugger

The Visual Studio Debugger and Immediate Window are invaluable tools for any sort of reflection based project. Putting watches on the objects and running GetProperty() and GetType() checks in the immediate window can help you quickly determine where you’re code’s gone wrong. On occasion, it will lead you astray (See Tip #1), but for the most part the debugging environment will expose other internals and values that will help push the project along. 

Scott Guthrie’s blog post on debugging tips is a great primer to using the debugger for these sorts of tasks.

Comments

  • Anonymous
    March 22, 2012
    TL;DR