Вот полная реализация решения, описанного другими на этой странице здесь и здесь .Этот код позволяет вам импортировать или «жестко кодировать» любую ссылку на живой объект, которую вы можете предоставить, в поток IL
DynamicMethod
как постоянно записанный 32- или 64-битный литерал.
Обратите внимание, что это явно не рекомендуемая методика, и она показана здесь только в образовательных и / или экспериментальных целях
Какотмеченный в одном из комментариев , вам не нужно пин GCHandle
;для GC вполне нормально перемещать объект как обычно, поскольку числовое значение дескриптора не изменится, пока экземпляр остается живым.Реальная причина, по которой вам нужен GCHandle
, заключается в том, что завершенный экземпляр DynamicMethod
будет не содержать ссылку на встроенный дескриптор (или даже не знать о нем)внутри себя.Без GCHandle,
экземпляр можно было бы собрать , когда / если все другие ссылки на него выходят за рамки.
Этот код ниже использует слишком осторожный подход, преднамеренно отказываясь от GCHandle
struct (то есть, не освобождая ее) после использования для извлечения ссылки на объект.Это означает, что целевой экземпляр никогда не будет собран.Если у вас есть специальные знания о вашем конкретном приложении, которое позволяет вам это делать, не стесняйтесь использовать некоторые другие средства, гарантирующие, что цель выживет и / / все конкретные DynamicMethod
, в которые она излучается с помощью этой техники;в этом случае вы можете освободить GCHandle
(код, показанный закомментированным) после его использования для получения значения дескриптора.
/// <summary>
/// Burn an reference to the specified runtime object instance into the DynamicMethod
/// </summary>
public static void Emit_LdInst<TInst>(this ILGenerator il, TInst inst)
where TInst : class
{
var gch = GCHandle.Alloc(inst);
var ptr = GCHandle.ToIntPtr(gch);
if (IntPtr.Size == 4)
il.Emit(OpCodes.Ldc_I4, ptr.ToInt32());
else
il.Emit(OpCodes.Ldc_I8, ptr.ToInt64());
il.Emit(OpCodes.Ldobj, typeof(TInst));
/// Do this only if you can otherwise ensure that 'inst' outlives the DynamicMethod
// gch.Free();
}
Вклад этого ответа, не упомянутого другими, заключается в том, что вы должны использовать Opcodes.Ldobj
инструкция для принудительного приведения правильного времени выполнения Type
в новый жестко запрограммированный литерал, как показано выше.Легко убедиться, что это хорошая практика, с помощью следующей последовательности испытаний.Он выдает bool
, указывающий, соответствует ли System.Type
только что импортированного экземпляра тому, что мы ожидаем, и возвращает true
, только когда в методе расширения, показанном выше, присутствует инструкция Opcodes.Ldobj
is.
TInst _inst = new MyObject();
// ...
il.Emit_LdInst(_inst); // <-- the function shown above
il.Emit(OpCodes.Isinst, typeof(TInst));
il.Emit(OpCodes.Ldnull);
il.Emit(OpCodes.Ceq);
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Xor);
То, что не кажется необходимым после нашего грубого Ldc_I4
/ Ldc_I8
пуша Conv_I
, и это похоже на правду, даже если мы отбросим IntPtr.Size
проверка и просто используйте Ldc_I8
, чтобы всегда загружать long
, даже на x86.Это снова благодаря Opcodes.Ldobj
сглаживанию таких нарушений.
Вот еще один пример использования.Он проверяет равенство ссылок между встроенным экземпляром (импортированным во время создания DynamicMethod) и любыми объектами ссылочного типа, которые могут быть по-разному предоставлены при вызове этого метода в любое время в будущем.Возвращает true
только когда появляется его давно потерянный прародитель.(Интересно, как могло бы произойти воссоединение ...)
il.Emit_LdInst(cmp);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ceq);
Наконец, замечание об ограничении where TInst : class
на метод расширения, показанное вверху.Во-первых, нет смысла импортировать тип значения таким образом, поскольку вместо этого вы можете просто импортировать его поля как литералы.Вы, возможно, заметили, однако, что решающее значение Opcodes.Ldobj
, которое делает импорт более надежным, задокументировано как предназначенное для типов значений, а не ссылочных типов, как мы делаем здесь.Простой ключ к этому - помнить, что на самом деле ссылка на объект - это дескриптор, который сам по себе является просто 32- или 64-битным шаблоном, который всегда копируется по значению.Другими словами, в основном ValueType
.
Я довольно широко протестировал все это на x86 и x64 , отладке и выпуске, и это работаетотлично пока без проблем.