Можно ли косвенно загрузить тип значения в стек

StackOverflow https://stackoverflow.com/questions/76274

  •  09-06-2019
  •  | 
  •  

Вопрос

В Microsoft IL для вызова метода для типа значения вам нужна косвенная ссылка.Допустим, у нас есть ILGenerator с именем "il" и что в настоящее время у нас есть значение Null в верхней части стека, если мы хотим проверить, имеет ли оно значение, мы могли бы выдать следующее:

var local = il.DeclareLocal(typeof(Nullable<int>));
il.Emit(OpCodes.Stloc, local);
il.Emit(OpCodes.Ldloca, local);
var method = typeof(Nullable<int>).GetMethod("get_HasValue");
il.EmitCall(OpCodes.Call, method, null);

Однако было бы неплохо пропустить сохранение его как локальной переменной и просто вызвать метод по адресу переменной, уже находящейся в стеке, что-то вроде:

il.Emit(/* not sure */);
var method = typeof(Nullable<int>).GetMethod("get_HasValue");
il.EmitCall(OpCodes.Call, method, null);

Семейство инструкций ldind выглядит многообещающе (особенно ldind_ref), но я не могу найти достаточной документации, чтобы узнать, приведет ли это к блокировке значения, что, как я подозреваю, возможно.

Я просмотрел выходные данные компилятора C #, но для достижения этой цели он использует локальные переменные, что заставляет меня поверить, что первый способ может быть единственным.У кого-нибудь есть идеи получше?

**** Редактировать:Дополнительные примечания ****

Попытка вызвать метод напрямую, как в следующей программе с закомментированными строками, не сработает (ошибка будет "Операция может дестабилизировать среду выполнения").Раскомментируйте строки, и вы увидите, что это работает так, как ожидалось, возвращая "True".

var m = new DynamicMethod("M", typeof(bool), Type.EmptyTypes);
var il = m.GetILGenerator();
var ctor = typeof(Nullable<int>).GetConstructor(new[] { typeof(int) });
il.Emit(OpCodes.Ldc_I4_6);
il.Emit(OpCodes.Newobj, ctor);
//var local = il.DeclareLocal(typeof(Nullable<int>));
//il.Emit(OpCodes.Stloc, local);
//il.Emit(OpCodes.Ldloca, local);
var getValue = typeof(Nullable<int>).GetMethod("get_HasValue");
il.Emit(OpCodes.Call, getValue);
il.Emit(OpCodes.Ret);
Console.WriteLine(m.Invoke(null, null));

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

Чего я хотел бы достичь (или узнать, возможно ли это), так это заменить три строки, которые показаны закомментированными, но сохранить работу программы без использования временной локальной.

Это было полезно?

Решение

Если переменная уже находится в стеке, вы можете пойти дальше и просто выполнить вызов метода.

Похоже, что конструктор не помещает переменную в стек в типизированной форме.Немного покопавшись в IL, выясняется, что есть два способа использования переменной после ее создания.

Вы можете загрузить переменную, которая будет хранить ссылку, в стек вычисления перед вызовом конструктора, а затем снова загрузить эту переменную после вызова конструктора следующим образом:

DynamicMethod method = new DynamicMethod("M", typeof(bool), Type.EmptyTypes);
ILGenerator il = method.GetILGenerator();
Type nullable = typeof(Nullable<int>);
ConstructorInfo ctor = nullable.GetConstructor(new Type[] { typeof(int) });
MethodInfo getValue = nullable.GetProperty("HasValue").GetGetMethod();
LocalBuilder value = il.DeclareLocal(nullable);         

// load the variable to assign the value from the ctor to
il.Emit(OpCodes.Ldloca_S, value);
// load constructor args
il.Emit(OpCodes.Ldc_I4_6);
il.Emit(OpCodes.Call, ctor);
il.Emit(OpCodes.Ldloca_S, value);

il.Emit(OpCodes.Call, getValue);
il.Emit(OpCodes.Ret);
Console.WriteLine(method.Invoke(null, null));

Другой вариант - делать это так, как вы показали.Единственная причина этого, которую я вижу, заключается в том, что методы ctor возвращают void, поэтому они не помещают свое значение в стек, как другие методы.Кажется странным, что вы можете вызвать Setloc, если нового объекта нет в стеке.

Другие советы

После еще одного рассмотрения вариантов и дальнейшего рассмотрения, я думаю, вы правы, предполагая, что это невозможно сделать.Если вы изучите поведение стека команд MSIL, вы увидите, что ни одна операция не оставляет своих операндов в стеке.Поскольку это было бы требованием для операции "получить адрес записи стека", я совершенно уверен, что таковой не существует.

Это оставляет вас либо с dup + box, либо с stloc + ldloca.Как вы уже отметили, последнее, скорее всего, более эффективно.

@грег:Многие инструкции оставляют свои Результат в стеке, но никакие инструкции не оставляют ни одной из своих операнды в стеке, что потребовалось бы для инструкции "получить адрес элемента стека".

Я все понял!К счастью, я читал о unbox код операции и заметил, что он выталкивает адрес о ценности. unbox.any выводит фактическое значение.Итак, чтобы вызвать метод для типа значения без необходимости сохранять его в локальной переменной и затем загружать его адрес, вы можете просто box за которым следует unbox.Используя ваш последний пример:

var m = new DynamicMethod("M", typeof(bool), Type.EmptyTypes);
var il = m.GetILGenerator();
var ctor = typeof(Nullable<int>).GetConstructor(new[] { typeof(int) });
il.Emit(OpCodes.Ldc_I4_6);
il.Emit(OpCodes.Newobj, ctor);
il.Emit(OpCodes.Box, typeof(Nullable<int>)); // box followed by unbox
il.Emit(OpCodes.Unbox, typeof(Nullable<int>));
var getValue = typeof(Nullable<int>).GetMethod("get_HasValue");
il.Emit(OpCodes.Call, getValue);
il.Emit(OpCodes.Ret);
Console.WriteLine(m.Invoke(null, null));

Недостатком этого является то, что упаковка приводит к выделению памяти для упакованного объекта, поэтому это немного медленнее, чем использование локальных переменных (которые уже были бы выделены).Но это избавляет вас от необходимости определять, объявлять и ссылаться на все необходимые вам локальные переменные.

Просто написал класс, который делает то, о чем просит OP...вот IL-код, который создает компилятор C #:

  IL_0008:  ldarg.0
  IL_0009:  ldarg.1
  IL_000a:  newobj     instance void valuetype [mscorlib]System.Nullable`1<int32>::.ctor(!0)
  IL_000f:  stfld      valuetype [mscorlib]System.Nullable`1<int32> ConsoleApplication3.Temptress::_X
  IL_0014:  nop
  IL_0015:  ret
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top