I've been writing a fair bit of Reflection.Emit code recently, and along the way have developed quite a number of extension methods for the ILGenerator class to handle the complex semantics of some seemingly trivial operations, and choose the most efficient op-codes (e.g. when can the short forms be used). I had already written EmitLdc methods to handle constant values for every type that can be represented as constant, but then came across the slightly odd circumstance where I had values that I knew could be emitted as constants, but they were in the form of a boxed System.Object instance so couldn't directly bind to the correct method.
The obvious solution is to add a new EmitLdc overload that takes a plain object as an argument and performs manual casting and dispatch to the correct overload at runtime. Unfortunately as C# doesn't allow types to be used in switch statements, and doesn't support pattern matching, there's no trivial way to do this except with a very large if/else statement or heavily nested ternary operators. These are both a bit inefficient and look clumsy, so a better option is with a static dictionary of EmitLdc methods keyed by type.
Fortunately C# 3.0 introduced object initializers and lamda expressions which allow this dictionary to be built in a very concise and declarative manner:
private static readonly IDictionary<Type, Action<ILGenerator, object>> emitLdcMethods =
new Dictionary<Type, Action<ILGenerator, object>>
{
{ typeof(bool), (il, val) => il.EmitLdc((bool)val) },
{ typeof(sbyte), (il, val) => il.EmitLdc((sbyte)val) },
{ typeof(byte), (il, val) => il.EmitLdc((byte)val) },
{ typeof(short), (il, val) => il.EmitLdc((short)val) },
{ typeof(ushort), (il, val) => il.EmitLdc((ushort)val) },
{ typeof(int), (il, val) => il.EmitLdc((int)val) },
{ typeof(uint), (il, val) => il.EmitLdc((uint)val) },
{ typeof(long), (il, val) => il.EmitLdc((long)val) },
{ typeof(ulong), (il, val) => il.EmitLdc((ulong)val) },
{ typeof(float), (il, val) => il.EmitLdc((float)val) },
{ typeof(double), (il, val) => il.EmitLdc((double)val) },
{ typeof(char), (il, val) => il.EmitLdc((char)val) },
{ typeof(decimal), (il, val) => il.EmitLdc((decimal)val) }
};
The method can then be written to perform a key lookup from the dictionary based on the type of the value being emitted, and fail otherwise.
public static void EmitLdc(this ILGenerator il, object value)
{
var valueType = value.GetType();
if (emitLdcMethods.ContainsKey(valueType))
{
var emitLdc = emitLdcMethods[valueType];
emitLdc(il, value);
}
else if (valueType.IsEnum)
{
EmitLdc(il, (Enum)value);
}
else
{
throw new NotSupportedException("The value cannot be emitted as a constant.");
}
}The eagle-eyed will notice that there's a special case for enumerated types, because these can be emitted as constants (being based on a CLR primitive type) but retrieving the type of an
Enum object will give the enumeration type, not the underlying type or
System.Enum, so won't work with the dictionary approach. The
EmitLdc method for enums simply uses
Enum.GetUnderlyingType and then uses the same lookup dictionary with that type.
Posted
Jul 20 2008, 11:51 PM
by
Greg Beech