Sunday, May 24, 2009

Evil Mutant Value Types

Setting a stucture's property through Reflection

Not so long ago I was presented with the following problem. Consider the following struct:

public struct UglyMutableStruct
{

    private int x;

    public UglyMutableStruct(int x)
    {
        this.x = x;
    }

    public int X
    {
        get { return this.x; }
        set { this.x = value; }
    }

    public override string ToString()
    {
        return string.Format("X: {0}", X);
    }

}

And the simple task was to set X's value through reflection:

UglyMutableStruct instance = new UglyMutableStruct(10);
Console.WriteLine(instance);

Type myType = typeof(UglyMutableStruct);

PropertyInfo propertyX = myType.GetProperty("X");
propertyX.SetValue(instance, 15, null);

Console.WriteLine(instance);

Output:

X: 10
X: 10
Press any key to continue . . .

Puzzled by this seemingly bizarre behavior, I searched the web to find a solution. I found a couple of people saying that it just can't be done, live with it. What I didn't find and wanted to know is why it can't be done. So, since I saw little analysis to be done to this particular bit of code I checked out the CIL through ildasm to see what could be happening. I came to my attention that instance was being boxed at the SetValue method, but never unboxed. OK, so let's try doing the boxing/unboxing ourselves.

UglyMutableStruct instance = new UglyMutableStruct(10);
Console.WriteLine(instance);

Object objectInstance = instance;
Type myType = typeof(UglyMutableStruct);

PropertyInfo propertyX = myType.GetProperty("X");
propertyX.SetValue(objectInstance, 15, null);
instance = (UglyMutableStruct)objectInstance;

Console.WriteLine(instance);

Output:

X: 10
X: 15
Press any key to continue . . .

It works. That was the problem! I left it at that, but something still felt not quite right.

What was really going on is that looking at the tree, I missed the forest. I saw reflection and thought that the underlying problem had to be reflection. Never stopped to think that when you pass a value type as a parameter the CLR sends a copy, and not the actual instance. The value of it was being set alright, but to the copy and not to my instance. It was just the expected behavior, just like this:

static void Main(string[] args)
{
    UglyMutableStruct instance = new UglyMutableStruct(10);
    Console.WriteLine(instance);
    SetValueToStruct(instance);
    Console.WriteLine(instance);
}

static void SetValueToStruct(UglyMutableStruct aCopy)
{
    Console.WriteLine("   --- Start method's scope ---");
    Console.WriteLine("   {0}", aCopy);
    aCopy.X = 15;
    Console.WriteLine("   {0}", aCopy);
    Console.WriteLine("   --- End method's scope ---");
}

Output:

X: 10
   --- Start method's scope --
   X: 10
   X: 15
   --- End method's scope --
X: 10
Press any key to continue . . .

The output is just as one would expect and it is exactly what was happening earlier.

This is yet another reason why mutable structs are plain evil. Only use them when performance is an issue.

Random thoughts: Well, my first thought is why on earth would someone be using reflection on a known type? Someone may want to keep things as abstract as they can to lower coupling. This is if you know that sometime in the future the actual property setting will be on an unknown type, but still if that comes to happen, you will be handling an object anyways, wouldn't you? Another scenario would be setting a large amount of data (many properties) and you'd like to keep the code clean. Still having a large amount of data kind of defeats the whole purpose of using a struct. So my conclusion is that this scenario is not seen very often, and if it happens it probably means that you need to rethink your design rather than make it work. But what's more important is that using mutable structs sooner or later means facing some unexpected behavior. If you have to use one, keep always in mind that you are.

No comments:

Post a Comment