Getting stuck in the weeds

While plowing through some AutoMapper support issues this weekend, I got a rather troubling one, where a developer got a rather scary exception message:

“Operation could destabilize the runtime”

Well that’s disturbing.  It all came about because they were trying to map to a value type, a scenario which I assumed I supported.  But, there weren’t any specific tests against this scenario, so you couldn’t map to something like this:

public struct IAmAStruct
{
    public string Value { get; set; }
}

We’ll put aside the fact that value types should be immutable for now, but this was a scenario AutoMapper supported…until I started making the performance improvements of using DynamicMethod.  But let’s review what I did, I wrapped the call to the real setter method with one that was loosely typed, giving me a method that would look something like this:

private void DoIt(object source, object value)
{
    ((Source)source).Total = (int)value;
}

Now suppose the Source type is a value type, and not a reference type.  How does the value get passed in?  First, it gets boxed to an object.  Then, inside the method, it would get unboxed, and the Total property is set.  At this point, I could have stepped back and realized the impossibility of it all, that all that boxing and unboxing creates copies.  But no, I prolonged the insanity.  My next trick was to use a reference parameter, and fill in the IL for it:

private void DoIt4(ref object source, object value)
{
    var valueSource = (ValueSource) source;
    valueSource.Value = (string) value;
}

Remember, I have to use “object” as the parameter types because I’m doing a late-bound method here.  I don’t know the types until runtime, so I have to be able to use high performing calls on something that is of type Object.  But do you see the problem here?  Because I unbox “source”, I create a new ValueSource object into the “valueSource” variable.  I change the Value property on it, but not the original source object passed in.

All of this I find out after I futz around with IL for waaaaaay too long.  But, had I actually tried to create a unit test around the regular, plain jane non-IL’d method, I would have quickly seen that I was trying to attempt the impossible.  Value types aren’t reference types, and I shouldn’t try to treat them that way.

Instead, I went back to the regular reflection, MethodInfo.Invoke way.  I could try the “out” parameter way…but I was tired of wasting my time.

Moral of the story?  Don’t get stuck in the weeds!  At any point during my long, pointless journey, I could have stopped and looked to see if what I was trying to do made any sense.  Instead, I got a stack trace and dove headlong into devising the most complex solution imaginable – hand-rolling IL.

Sigh.

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Jimmy Bogard

I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
This entry was posted in C#, Design. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Eric Todd

    No samples seem to demonstrate using DynamicMethod with value types.
    I think this should solve your problem. I’ve tried to extract this snippet from my codebase in the simplest way possible. I do a lot of dynamic reflection and ran into this problem a while back.

    var dynamicMethod = GetDynamicMethod(); // Pick your own implementation
    var generator = dynamicMethod.GetILGenerator();
    var method = property.GetSetMethod(true);

    if(method != null)
    {

    if(!method.IsStatic)
    {
    generator.LoadArgument(0);

    if(method.DeclaringType.IsValueType)
    {
    generator.Emit(OpCodes.Unbox,method.DeclaringType);
    }
    generator.LoadArgument(1);
    }
    else
    {
    generator.LoadArgument(0);
    }

    if(method.DeclaringType.IsValueType)
    {
    generator.Emit(OpCodes.Unbox_Any,method.DeclaringType);
    }

    if((method.CallingConvention & CallingConventions.VarArgs) != 0)
    {
    if(method.IsFinal || !method.IsVirtual)
    {
    generator.EmitCall(OpCodes.Call,method,null);
    }
    else
    {
    generator.EmitCall(OpCodes.Callvirt,method,null);
    }
    }
    else
    {
    if(method.IsFinal || !method.IsVirtual)
    {
    generator.Emit(OpCodes.Call,method);
    }
    else
    {
    generator.Emit(OpCodes.Callvirt,method);
    }
    }
    generator.Emit(OpCodes.Ret);

    }