Язык программирования C#9 и платформа .NET5 - Троелсен Эндрю
Формально сущность, используемая для хранения набора вычисляемых значений, называется виртуальным стеком выполнения. Вы увидите, что CIL предоставляет несколько кодов операций, которые служат для помещения значения в стек; такой процесс именуется загрузкой. Кроме того, в CIL определены дополнительные коды операций, которые перемещают самое верхнее значение из стека в память (скажем, в локальную переменную), применяя процесс под названием сохранение.
В мире CIL невозможно напрямую получать доступ к элементам данных, включая локально определенные переменные, входные аргументы методов и данные полей типа. Вместо этого элемент данных должен быть явно загружен в стек и затем извлекаться оттуда для использования в более позднее время (запомните упомянутое требование, поскольку оно содействует пониманию того, почему блок кода CIL может выглядеть несколько избыточным).
На заметку! Вспомните, что код CIL не выполняется напрямую, а компилируется по требованию. Во время компиляции кода CIL многие избыточные аспекты реализации оптимизируются. Более того, если для текущего проекта включена оптимизация кода (на вкладке Build (Сборка) окна свойств проекта в Visual Studio), то компилятор будет также удалять разнообразные избыточные детали CIL.
Чтобы понять, каким образом CIL задействует модель обработки на основе стека, создайте простой метод C# по имени
PrintMessage()
void
void PrintMessage()
{
string myMessage = "Hello.";
Console.WriteLine(myMessage);
}
Если просмотреть код CIL, который получился в результате трансляции метода
PrintMessage()
.locals
ldstr
stloc.0
0
Далее с помощью кода операции
ldloc.0
0
System.Console.WriteLine()
call
ret
PrintMessage()
nop
.method assembly hidebysig static void PrintMessage() cil managed
{
.maxstack 1
<b> // Определить локальную переменную типа string (по индексу 0).</b>
.locals init ([0] string V_0)
<b> // Загрузить в стек строку со значением "Hello."</b>
ldstr " Hello."
<b> // Сохранить строковое значение из стека в локальной переменной.</b>
stloc.0
<b> // Загрузить значение по индексу 0.</b>
ldloc.0
<b> // Вызвать метод с текущим значением.</b>
call void [System.Console]System.Console::WriteLine(string)
ret
}
На заметку! Как видите, язык CIL поддерживает синтаксис комментариев в виде двойной косой черты (и вдобавок синтаксис
/*...*/
Теперь, когда вы знаете основы директив, атрибутов и кодов операций CIL, давайте приступим к практическому программированию на CIL, начав с рассмотрения темы возвратного проектирования.
Возвратное проектирование
В главе 1 было показано, как применять утилиту
ildasm.exe
ilasm.exe
Выражаясь формально, такой прием называется возвратным проектированием и может быть полезен в избранных обстоятельствах, которые перечислены ниже.
• Вам необходимо модифицировать сборку, исходный код которой больше не доступен.
• Вы работаете с далеким от идеала компилятором языка .NET Core, который генерирует неэффективный (или явно некорректный) код CIL, поэтому нужно изменять кодовую базу.
• Вы конструируете библиотеку взаимодействия с СОМ и хотите учесть ряд атрибутов COM IDL, которые были утрачены во время процесса преобразования (такие как COM-атрибут
[helpstring]
Чтобы ознакомиться с процессом возвратного проектирования, создайте новый проект консольного приложения .NET Core на языке C# по имени
RoundTrip
dotnet new console -lang c# -n RoundTrip -o .RoundTrip -f net5.0
Модифицируйте операторы верхнего уровня, как показано ниже:
<b>// Простое консольное приложение С#.</b>
Console.WriteLine("Hello CIL code!");
Console.ReadLine();
Скомпилируйте программу с применением интерфейса CLI:
dotnet build
На заметку! Вспомните из главы 1, что результатом компиляции всех сборок .NET Core (библиотек классов и консольных приложений) будут файлы с расширением
*.dll
dotnet.exe
RoundTrip.exe
RoundTrip.dll
dotnet.exe
RoundTrip.exe
Roundtrip.dll