For performance reasons, I am trying to write an IL delegate that can return a random KeyValuePair
from a dictionary without having to use enumeration. To do this, I am directly reading from the dictionary's _bucket
and _entries
fields.
Dictionary
is a generic collection, but I would like to avoid having to compile a delegate for each individual type and just return a boxed object instead. However, it appears that working with generics in this manner is a bit troublesome.
Here's where I'm currently at. Please note that the implementation is not complete:
public static class DictionaryExtensions
{
private delegate object RandomDelegate( IDictionary dict, Random random );
private static RandomDelegate randomDel;
static DictionaryExtensions()
{
randomDel = CompileRandomDel();
}
public static object RandomValue<TKey, TValue>( this Dictionary<TKey, TValue> dict, Random rand )
{
var x = randomDel( dict, rand );
return x;
}
private static RandomDelegate CompileRandomDel()
{
var bucketsField = typeof( Dictionary<,> ).GetField( "_buckets", BindingFlags.Instance | BindingFlags.NonPublic );
var entriesField = typeof( Dictionary<,> ).GetField( "_entries", BindingFlags.Instance | BindingFlags.NonPublic );
var randNext = typeof( Random ).GetMethod( "Next", Type.EmptyTypes );
var method = new DynamicMethod(
"RandomEntry",
MethodAttributes.Public | MethodAttributes.Static,
CallingConventions.Standard,
typeof( object ),
new[] { typeof( Dictionary<,> ), typeof( Random ) },
typeof( DictionaryExtensions ),
false );
var il = method.GetILGenerator();
il.DeclareLocal( typeof( int ) ); // Loc_0: Bucket
il.Emit( OpCodes.Ldarg_1 ); // Load random
il.Emit( OpCodes.Call, randNext ); // Get next random int
//il.Emit( OpCodes.Ldarg_0 ); // Load dictionary
//il.Emit( OpCodes.Ldfld, bucketsField ); // Load buckets
//il.Emit( OpCodes.Ldlen ); // Load buckets length
//il.Emit( OpCodes.Rem ); // random % bucket count
//il.Emit( OpCodes.Ldelem ); // Load bucket
//il.Emit( OpCodes.Stloc_0 ); // Store bucket in loc_0
//il.Emit( OpCodes.Ldarg_0 ); // Load dictionary
//il.Emit( OpCodes.Ldfld, entriesField ); // Load dictionary entries
//il.Emit( OpCodes.Ldloc_0 ); // Load bucket
//il.Emit( OpCodes.Ldelem ); // Load element at bucket
// Debug (just returning the random int for now)
il.Emit( OpCodes.Conv_I4 );
il.Emit( OpCodes.Box, typeof( int ) );
il.Emit( OpCodes.Ret );
return ( RandomDelegate ) method.CreateDelegate( typeof( RandomDelegate ) );
}
}
My problem appears to be the lack of specific generic arguments. Running the method results in a TypeInitialization: An attempt was made to load a program with an incorrect format. (Exception from HRESULT: 0x8007000B).
If I change the DynamicMethod's parameters from Dictionary<,>
to IDictionary
, this works. However, this is dangerous, as not all IDictionary
implementations are equal, and this delegate is meant only to be used on an actual Dictionary<,>
type. In addition to this, it appears that the use of IDictionary
might be screwing up the loading of fields, as it is throwing an InvalidProgramException
when I try to load just the length of the _entries
field and return it.
So, using a generic type without specifying parameters is a no-go. Is there any other way to compile a safe delegate that will work with all generic type arguments? Since the return value is just a boxed object
I don't see the need to compile a delegate for each type it comes across, but if it is unavoidable I suppose I'll have no choice.
User contributions licensed under CC BY-SA 3.0