#c# #delegates #cil
#c# #делегаты #cil
Вопрос:
По соображениям производительности я пытаюсь написать делегат 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
, я не вижу необходимости компилировать делегат для каждого встречающегося типа, но если это неизбежно, я полагаю, у меня не будет выбора.
Комментарии:
1. Короче говоря: Нет, не полностью, из-за специализации кода, которая может произойти с универсальными типами. Смотрите здесь: joeduffyblog.com/2011/10/23/… (По сути, один и тот же универсальный тип, использующий разные параметры универсального типа, не обязательно использует общий код, что сделало бы невозможным использование одного делегата для указания только на одну запись метода, поскольку могут существовать разные базы кода в отношении разных параметров универсального типа) Если параметры универсального типа ограничены ссылочными типами, происходит совместное использование кода…
2. Почему вы против создания нового динамического метода для каждого типа словаря, с которым он используется? Это достаточно общий и, возможно, самый простой в реализации. Другим вариантом было бы создать динамическую сборку с универсальным типом / методом, и единственная разница в производительности при этом заключается в вызове динамического метода и метода в динамической сборке.