Логичным дополнением к прошлой статье был-бы наглядный пример, хотя-бы для того, чтобы показать что не так страшен чёрт, как его рисуют, и на самом деле даже если вам необходимо собрать инициализатор массива через Reflection.Emit, то большинство лишних телодвижений возьмёт на себя API, а от вас остаётся, по большей части, только слизать придуманный компилятором код из прошлой статьи. В этом примере я ограничусь простым статическим массивом на 3 System.Int32 элемента.
Ну а начнём мы с того, с чего начинается едва ли не каждый пост про Reflection.Emit — создание динамической сборки, модуля и типа:
TypeBuilder l_typeBuilder = AppDomain.CurrentDomain.
DefineDynamicAssembly
(
new System.Reflection.AssemblyName("Reflection.Emit array initilization example"),
System.Reflection.Emit.AssemblyBuilderAccess.RunAndSave
).//новая динамическая сборка
DefineDynamicModule("Example").//обьявление модуля
DefineType("ExampleClass");//обьявление типа
Вот и готов TypeBuilder. Одним из странных моментов прошлой статьи были вложенный тип с директивой .size и соответствующее этому типу поле. Вам не придётся об этом заботиться, ибо над этим уже подумали в Microsoft: метод TypeBuilder.DefineInitializedData придумает и класс, и поле, запишет данные куда надо, и придумает всю мишуру, которая только может им пригодиться. Это, кстати, следующий шаг:
FieldBuilder l_fieldBuilder = l_typeBuilder.DefineInitializedData("initialization_data", new byte[]{ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00 }, System.Reflection.FieldAttributes.Static);
Я не стал заморачиваться с названием поля и обозвал его так, как обозвал. Как и должно быть, данными этого массива являются три числа (1,2,3) в little endian. Т.к. целевой массив статический, то и атрибут стоит соответствующий (Стоит заметить что это всё ещё не наш целевой массив). Обьявление целевого поля будет, думаю, многим уже знакомо:
FieldBuilder l_fieldBuilder2 = l_typeBuilder.DefineField("m_array", typeof(int[]), System.Reflection.FieldAttributes.Static | System.Reflection.FieldAttributes.Public);
Поле как поле, ничего не обычного: статическое и публичное. Но как и любое послушное (а может, и непослушное тоже) статическое поле, оно должно инициализироваться в статическом конструкторе. Посему, пришло время создавать тот самый статический конструктор. На этот момент у MS тоже заготовлена загогулина:
ConstructorBuilder l_cb = l_typeBuilder.DefineTypeInitializer();
После этого остаётся только создать ILGenerator и написать тело метода. Код аналогичен коду из прошлой статьи, за исключенем того, что используются опкоды для статических полей:
ILGenerator l_propGetILGen = l_cb.GetILGenerator();
l_propGetILGen.Emit(OpCodes.Ldc_I4, 3);//отправляем размер массива в стек
l_propGetILGen.Emit(OpCodes.Newarr, typeof(int));//и создаём массив, размером в 3 элемента
l_propGetILGen.Emit(OpCodes.Dup);//дублируем ссылку. InitializeArray возвращает void, так что если этого не сделать, то ссылка на созданный массив просто пропадёт
l_propGetILGen.Emit(OpCodes.Ldtoken, l_fieldBuilder);//закидываем в стек токен инициализируемого поля. Поле хранит в себе RVA своего сегмента данных
l_propGetILGen.EmitCall(OpCodes.Call, new System.Action<Array, RuntimeFieldHandle>(System.Runtime.CompilerServices.RuntimeHelpers.InitializeArray).Method, new Type[] { });//и записываем данные в поле (всё ещё не целевое)
l_propGetILGen.Emit(OpCodes.Stsfld, l_fieldBuilder2); // закидываем значение из стека(массив) в наше целевое статическое поле
l_propGetILGen.Emit(OpCodes.Ret);
Вот и всё. Ответ на вопрос «Для чего два массива?» скорее всего должен быть такой — для обеспечения потокобезопасности, значение целевого массива должно быть изменено атомарной операцией.
Полный код:
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection.Emit;
namespace ReflectionEmitArrayInitializerExample
{
class Program
{
static void Main(string[] args)
{
TypeBuilder l_typeBuilder = AppDomain.CurrentDomain.
DefineDynamicAssembly
(
new System.Reflection.AssemblyName("Reflection.Emit array initilization example"),
System.Reflection.Emit.AssemblyBuilderAccess.RunAndSave
).
DefineDynamicModule("Example").
DefineType("ExampleClass");
FieldBuilder l_fieldBuilder = l_typeBuilder.DefineInitializedData("initialization_data", new byte[]{ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00 }, System.Reflection.FieldAttributes.Static);
FieldBuilder l_fieldBuilder2 = l_typeBuilder.DefineField("m_array", typeof(int[]), System.Reflection.FieldAttributes.Static | System.Reflection.FieldAttributes.Public);
ConstructorBuilder l_cb = l_typeBuilder.DefineTypeInitializer();
ILGenerator l_propGetILGen = l_cb.GetILGenerator();
l_propGetILGen.Emit(OpCodes.Ldc_I4, 3);
l_propGetILGen.Emit(OpCodes.Newarr, typeof(int));
l_propGetILGen.Emit(OpCodes.Dup);
l_propGetILGen.Emit(OpCodes.Ldtoken, l_fieldBuilder);
l_propGetILGen.EmitCall(OpCodes.Call, new System.Action<Array, RuntimeFieldHandle>(System.Runtime.CompilerServices.RuntimeHelpers.InitializeArray).Method, new Type[] { });
l_propGetILGen.Emit(OpCodes.Stsfld, l_fieldBuilder2);
l_propGetILGen.Emit(OpCodes.Ret);
System.Type l_t = null;
System.Reflection.FieldInfo l_pi = (l_t = l_typeBuilder.CreateType()).GetField("m_array");
int[] l_array = (int[])l_pi.GetValue(null);
}
}
}
l_array станет массивом с тремя элементами {1,2,3}.
Автор: 6opoDuJIo