Friday 27 June 2008

Reflection and ValueTypes

Recently I had an interesting case with reflection and value types. One of my colleagues created a struct that contained the configuration for his module. It just had a bunch of public properties and private underlying fields:

public struct MyStruct
{
private string field;
public string Field
{
get { return field; }
set { field = value; }
}
}

So the idea was that this thing
1. Needed to be mutable, because we wanted to use a PropertyGrid control to change it
2. My colleague wanted to have it as a ValueType, so that after it was passed to his class in an initialize method, no-one could change it (without calling the initialize method again, which would create the next copy on his heap).

Usually to cater for (1) I would just create a class looking the same as his struct, and to sort out (2) I would create a immutable type (probably class as well, but it doesn’t matter that much for immutable types). However this mutable struct seems the only way to have one type to cover both of these points and it worked quite well in the scenario with the PropertyGrid.

However, a few days later I wanted to dynamically created one of these in some other piece of code using reflection manually. I wrote the following method (simplified):

MyStruct CreateMyStruct(string propertyName, string value)
{
MyStruct result = new MyStruct();
Type myType = result.GetType();
PropertyInfo property = myType.GetProperty(propertyName);
property.SetValue(result, value, new object[] {});
return result;
}

But it didn’t work. The result was empty! The property wasn’t set and no exceptions were thrown.

As you probably guessed, the problem here is boxing. PropertyInfo.SetValue expects an object, so our structure got boxed before it was passed to the SetValue method. So SetValue set the value on the boxed copy of the structure, which got discarded after the call returned. The trick to get around this issue is to get the ValueType boxed earlier, so that we can get hold of the boxed object before it is discarded. The following workaround worked fine:

MyStruct CreateMyStructFixed(string propertyName, string value)
{
MyStruct result = new MyStruct();
result = (MyStruct) SetValue(result, propertyName, value);
return result;
}

public static object SetValue(object o, string name, object value)
{
PropertyInfo info = o.GetType().GetProperty(name);
info.SetValue(o, value, null);
return o;
}

Please note that we need to set the result to the boxed copy that was actually changed. When I took another look at the initial code with PropertyGrid, because I implemented some level of indirection, I was actually doing the same think without realising it!