Search code examples
c#.netreflection.emitil

Emit IL code to load a decimal value


I have code like this to emit IL code that loads integer or string values. But I don't know how to add the decimal type to that. It isn't supported in the Emit method. Any solutions to this?

ILGenerator ilGen = methodBuilder.GetILGenerator();
if (type == typeof(int))
{
    ilGen.Emit(OpCodes.Ldc_I4, Convert.ToInt32(value, CultureInfo.InvariantCulture));
}
else if (type == typeof(double))
{
    ilGen.Emit(OpCodes.Ldc_R8, Convert.ToDouble(value, CultureInfo.InvariantCulture));
}
else if (type == typeof(string))
{
    ilGen.Emit(OpCodes.Ldstr, Convert.ToString(value, CultureInfo.InvariantCulture));
}

Not working:

else if (type == typeof(decimal))
{
    ilGen.Emit(OpCodes.Ld_???, Convert.ToDecimal(value, CultureInfo.InvariantCulture));
}

Edit: Okay, so here's what I did:

else if (type == typeof(decimal))
{
    decimal d = Convert.ToDecimal(value, CultureInfo.InvariantCulture);
    // Source: https://msdn.microsoft.com/en-us/library/bb1c1a6x.aspx
    var bits = decimal.GetBits(d);
    bool sign = (bits[3] & 0x80000000) != 0;
    byte scale = (byte)((bits[3] >> 16) & 0x7f);
    ilGen.Emit(OpCodes.Ldc_I4, bits[0]);
    ilGen.Emit(OpCodes.Ldc_I4, bits[1]);
    ilGen.Emit(OpCodes.Ldc_I4, bits[2]);
    ilGen.Emit(sign ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
    ilGen.Emit(OpCodes.Ldc_I4, scale);
    var ctor = typeof(decimal).GetConstructor(new[] { typeof(int), typeof(int), typeof(int), typeof(bool), typeof(byte) });
    ilGen.Emit(OpCodes.Newobj, ctor);
}

But it doesn't generate a newobj opcode, but instead nop and stloc.0. The constructor is found and passed to the Emit call. What's wrong here? Obviously an InvalidProgramException is thrown when trying to execute the generated code because the stack is completely messed up.


Solution

  • Come on, just decompile some C# code that does the same thing - you'll see that there's no decimal primitive.

    42M
    

    compiles to

    ldc.i4.s    2A
    newobj      System.Decimal..ctor
    

    For a decimal number, this is much more complicated:

    42.3M
    

    gives

    ldc.i4      A7 01 00 00 
    ldc.i4.0    
    ldc.i4.0    
    ldc.i4.0    
    ldc.i4.1    
    newobj      System.Decimal..ctor
    

    The easiest way to get this for an arbitrary decimal is to use the int[] overload of the constructor and the GetBits static method. You could also reverse-engineer the SetBits method to allow you to call the simpler constructor with the proper values, or use reflection to read the internal state - there's plenty of options.

    EDIT:

    You're close, but you broke the ILGen - while the last argument to the constructor is a byte, the constant you're loading must be an int. The following works as expected:

    var bits = decimal.GetBits(d);
    bool sign = (bits[3] & 0x80000000) != 0;
    int scale = (byte)((bits[3] >> 16) & 0x7f);
    gen.Emit(OpCodes.Ldc_I4, bits[0]);
    gen.Emit(OpCodes.Ldc_I4, bits[1]);
    gen.Emit(OpCodes.Ldc_I4, bits[2]);
    gen.Emit(sign ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
    gen.Emit(OpCodes.Ldc_I4, scale);
    var ctor = typeof(decimal).GetConstructor(new[] { typeof(int), typeof(int), 
                                                    typeof(int), typeof(bool), typeof(byte) });
    gen.Emit(OpCodes.Newobj, ctor);
    gen.Emit(OpCodes.Ret);
    

    EDIT 2:

    A simple example of how you can use expression trees (in this case the tree is built by the C# compiler, but that's up to you) to define dynamic method bodies:

    var assembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("Test"), 
                                                         AssemblyBuilderAccess.Run);
    var module = assembly.DefineDynamicModule("Test");
    var type = module.DefineType("TestType");
    
    var methodBuilder = type.DefineMethod("MyMethod", MethodAttributes.Public 
                                                      | MethodAttributes.Static);
    methodBuilder.SetReturnType(typeof(decimal));
    
    Expression<Func<decimal>> decimalExpression = () => 42M;
    
    decimalExpression.CompileToMethod(methodBuilder);
    
    var t = type.CreateType();
    
    var result = (decimal)t.GetMethod("MyMethod").Invoke(null, new object[] {});
    
    result.Dump(); // 42 :)