I was writing some C# unit tests that had to use Reflection in order to set properties on objects, when I got into an interesting problem. I will provide a simplified version of the code I wrote, first the version without reflection, then my reflection version that had an issue and, in the end, the correct version.

TestClass is a class that has a property of type List<int>:

class TestClass
{
    public List<int> Value { get; set; }
}

Goal: create an instance of this class, set the property and print the second element in the list. Simple, huh? The code without reflection is:

TestClass c = new TestClass();
c.Value = new List<int>() { 4, 5, 6 };
Console.WriteLine(c.Value[1]);

Seems straight forward to use reflection for this, right? Here is my attempt:

//TestClass c = new TestClass();
object c = new TestClass();
//c.Value = new List<int>() { 4, 5, 6 };
Type t = c.GetType();
PropertyInfo prop = t.GetProperty("Value", BindingFlags.Public | BindingFlags.Instance);
prop.SetValue(c, new List<int>() { 4, 5, 6 }, null);
//Console.WriteLine(c.Value[1]);
int valueToOutput = (int)prop.GetValue(c, new object[] { 1 });
Console.WriteLine(valueToOutput);

Can you see the glitch? I can tell you that line 10 throws TargetParameterCountException. You know why?

Looking at the IL disassembled code for the program without reflection gives the answer (I added some comments for clarity and removed unnecessary lines):

//TestClass c = new TestClass();
IL_0001:  newobj     instance void ConsoleApplication1.TestClass::.ctor()
//c.Value = new List<int>() { 4, 5, 6 };
IL_0008:  newobj     instance void class [mscorlib]System.Collections.Generic.List`1<int32>::.ctor()
IL_0010:  callvirt   instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
IL_0018:  callvirt   instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
IL_0020:  callvirt   instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
IL_0027:  callvirt   instance void ConsoleApplication1.TestClass::set_Value(class [mscorlib]System.Collections.Generic.List`1<int32>)
//Console.WriteLine(c.Value[1]);
IL_002e:  callvirt   instance class [mscorlib]System.Collections.Generic.List`1<int32> ConsoleApplication1.TestClass::get_Value()
IL_0034:  callvirt   instance !0 class [mscorlib]System.Collections.Generic.List`1<int32>::get_Item(int32)
IL_0039:  call       void [mscorlib]System.Console::WriteLine(int32)

Line 3 in the original program gets translated to a property get in order to obtain the List<nt> object and then, on that object, the get_Item method is called with the same arguments as the indexed property. This is where I was wrong, I was calling the property with the arguments that were supposed to be for method and, of course, not invoking the method. The correct approach is (changed lines are highlighted):

//TestClass c = new TestClass();
object c = new TestClass();
//c.Value = new List<int>() { 4, 5, 6 };
Type t = c.GetType();
PropertyInfo prop = t.GetProperty("Value", BindingFlags.Public | BindingFlags.Instance);
prop.SetValue(c, new List<int>() { 4, 5, 6 }, null);
//Console.WriteLine(c.Value[1]);
object listObject = prop.GetValue(c, null);
MethodInfo mtd = listObject.GetType().GetMethod("get_Item", BindingFlags.Public | BindingFlags.Instance);
int valueToOutput = (int)mtd.Invoke(listObject, new object[] { 1 });
Console.WriteLine(valueToOutput);

In the end, two observations:

1. A method can’t have a default indexer and a method get_Item with the same argument. The following code will not compile because the method is defined twice.

class TestClass
{
    public int this[int index]
    {
        get
        {
            return 0;
        }
    }
    public int get_Item(int index)
    {
        return 0;
    }
}

2. You can replace indices with calls to get_Item. This method is hidden by the Visual Studio Intellisense but it perfectly legal.

TestClass c = new TestClass();
c.Value = new List<int>() { 4, 5, 6 };
//Equivalent with Console.WriteLine(c.Value[1]);
Console.WriteLine(c.Value.get_Item(1));

2 comments

  1. Nami on April 22nd, 2011 at 3:37 pm

    public int this[int index]
    {
    get
    {
    return Value[index];
    }
    set
    {
    Value[index] = value;
    }
    }

    public int Item
    {
    get
    {
    return 0;
    }
    set
    {

    }
    }

    This is also invalid :)

  2. Karin on June 29th, 2011 at 11:55 am

    When retrieving the indexer for a type, the important thing to remember is that it may not simply be “Item”. System.Runtime.CompilerServices.IndexerNameAttribute can be used to name the indexer whatever the dveloper wants it to be. I would not hardcode the method but instead use this:


    foreach (PropertyInfo property in type.GetProperties(
    BindingFlags.Instance | BindingFlags.Public))
    {
    if (property.GetIndexParameters().Length > 0)
    {
    Console.WriteLine("Found indexer for {0} - {1}",
    type, property.Name);
    }
    }

Leave a comment

Please write the comment in English!

Allowed HTML tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>