Greg Beech's Website

Emitting decimal pseudo-constants with Reflection.Emit

Some languages such as C# let you treat the System.Decimal type as an integral type most of the time, even having the keyword decimal, and will let you declare constant fields of the type under some circumstances. However, decimal isn't a primitive type in the same way as the other numerics, and is implemented quite differently from them. At CIL level, you can't actually declare a constant decimal in the normal way; you have to create an initonly field marked with DecimalConstantAttribute which is understood by the CLR.

The logic shown here is optimised to emit decimal constants declared within a method body, however the logic at the bottom of the method could also be used for the constructor of DecimalConstantAttribute. It's interesting to note that the C# 3.0 compiler does not special-case any of the static fields in the same way as this method does, which may be because there's such minimal performance difference that it isn't really worth the added complexity.

Other EmitLdc method calls within this are additional extension methods I implemented to cover other primitive data types, which do things such as special-casing the loading of the i4 type for the built-in constant values, and performing unchecked casting to emit unsigned values. If anyone wants me to post the code for them then I'm happy to do so, but they really are quite trivial.

public static void EmitLdc(this ILGenerator il, decimal value)
{
    const BindingFlags FieldFlags = BindingFlags.Public | BindingFlags.Static;
    if (value == decimal.Zero)
    {
        il.Emit(OpCodes.Ldsfld, typeof(decimal).GetField("Zero", FieldFlags));
    }
    else if (value == decimal.One)
    {
        il.Emit(OpCodes.Ldsfld, typeof(decimal).GetField("One", FieldFlags));
    }
    else if (value == decimal.MinusOne)
    {
        il.Emit(OpCodes.Ldsfld, typeof(decimal).GetField("MinusOne", FieldFlags));
    }
    else if (value == decimal.MinValue)
    {
        il.Emit(OpCodes.Ldsfld, typeof(decimal).GetField("MinValue", FieldFlags));
    }
    else if (value == decimal.MaxValue)
    {
        il.Emit(OpCodes.Ldsfld, typeof(decimal).GetField("MaxValue", FieldFlags));
    }
    else if (Math.Round(value) == value && value >= long.MinValue && value <= long.MaxValue)
    {
        // if the decimal is a whole number then we can optimise the construction by pushing the smallest
        // acceptable integer type value onto the stack and using the appropriate constructor
        if (value >= int.MinValue && value <= int.MaxValue)
        {
            EmitLdc(il, (int)value);
            il.Emit(OpCodes.Newobj, typeof(decimal).GetConstructor(new[] { typeof(int) }));
        }
        else
        {
            EmitLdc(il, (long)value);
            il.Emit(OpCodes.Newobj, typeof(decimal).GetConstructor(new[] { typeof(long) }));
        }
    }
    else
    {
        // the Decimal.GetBits method returns four integers, the first three of which are the lo/med/hi parts 
        // of the decimal and the fourth which is structured as follows:
        // -------------------------------------------------------------------------------------------------
        // |31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|12|11|10|09|08|07|06|05|04|03|02|01|00|                     
        // |ns|      not used      |         scale         |                   not used                    |
        // -------------------------------------------------------------------------------------------------
        // where ns is the negative sign (1 indicates negative) and the scale is a value between 1 and 28

        var parts = Decimal.GetBits(value);
        Debug.Assert(parts.Length == 4, "Expected the Decimal.GetBits method to return 4 parts");
        var negative = ((parts[3] >> 31) & 1) == 1;
        var scale = (parts[3] >> 16) & 0xFF;

        EmitLdc(il, parts[0]);
        EmitLdc(il, parts[1]);
        EmitLdc(il, parts[2]);
        EmitLdc(il, negative);
        EmitLdc(il, scale);
        il.Emit(OpCodes.Newobj, typeof(decimal).GetConstructor(
            new[] { typeof(int), typeof(int), typeof(int), typeof(bool), typeof(byte) }));
    }
}
There are optimisations that could be made here to make the emit code itself faster, namely caching the decimal constructors in static fields so we don't have to use reflection to find them each time. We could also set up a Dictionary<decimal, Action> containing the Ldsfld emit functions so there is only one specific-value check needed rather than five (which is essentially what a switch statement would do, but because a decimal isn't a real constant C# won't allow a switch statement based on it). For the sake of readability though, I've shown the code in a single method.

Posted Jul 07 2008, 01:14 AM by Greg Beech
Filed under: , ,

Comments

Greg Beech's Tech Blog wrote Emitting arbitrary constants with Reflection.Emit
on 07-20-2008 11:52 PM

I've been writing a fair bit of Reflection.Emit code recently, and along the way have developed quite

Add a Comment

(required)  
(optional)
(required)  
Remember Me?

Enter the numbers above:
Copyright (C) Greg Beech. All rights reserved.
Powered by Community Server (Non-Commercial Edition), by Telligent Systems