C# Generic DynamicMethod with no type parameters

1

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.

c#
delegates
cil
asked on Stack Overflow Mar 6, 2019 by Haus • edited Mar 6, 2019 by Haus

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0