C # универсальный DynamicMethod без параметров типа - PullRequest
1 голос
/ 06 марта 2019

По соображениям производительности я пытаюсь написать делегат IL, который может возвращать случайный KeyValuePair из словаря без использования перечисления. Для этого я читаю напрямую из полей _bucket и _entries словаря.

Dictionary - это универсальная коллекция, но я бы хотел избежать необходимости компилировать делегат для каждого отдельного типа и просто возвращать упакованный объект. Тем не менее, похоже, что работать с дженериками подобным образом немного хлопотно.

Вот где я сейчас нахожусь. Обратите внимание, что реализация не завершена:

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 ) );
    }

}

Моя проблема, по-видимому, заключается в отсутствии конкретных общих аргументов. Запуск метода приводит к TypeInitialization: An attempt was made to load a program with an incorrect format. (Exception from HRESULT: 0x8007000B).

Если я изменю параметры DynamicMethod с Dictionary<,> на IDictionary, это работает. Однако это опасно, поскольку не все реализации IDictionary равны, и этот делегат предназначен для использования только с фактическим типом Dictionary<,>. В дополнение к этому, кажется, что использование IDictionary могло бы испортить загрузку полей, так как это выдает InvalidProgramException, когда я пытаюсь загрузить только длину поля _entries и вернуть ее.

Таким образом, использование универсального типа без указания параметров не допускается. Есть ли другой способ скомпилировать безопасный делегат, который будет работать со всеми аргументами универсального типа? Так как возвращаемое значение - это просто в штучной упаковке object Я не вижу необходимости компилировать делегат для каждого типа это встречается, но если это неизбежно, я полагаю, у меня не будет выбора.

...