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.
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 :)