Последний аспект CIL-кода связан с ролью, которую играют различные коды операций. Вспомните, что под кодом операции понимается лексема CIL, используемая для построения логики реализации члена. Все поддерживаемые в CIL коды операций (которых немало) могут быть разделены на три основных категории:
- коды операций, позволяющие управлять выполнением программы;
- коды операций, позволяющие вычислять выражения;
- коды операций, позволяющие получать доступ к значениям в памяти (через параметры, локальные переменные и т.д.).
В таблице приведены некоторые наиболее полезные коды операций, имеющие непосредственное отношение к логике реализации членов (для удобства они сгруппированы по функциональности):
| Коды операций | Описание |
| add, sub, mul, div, rem | Позволяют выполнять сложение, вычитание, умножение и деление двух значений (rem возвращает остаток от деления) |
| and, or, not, xor | Позволяют выполнять соответствующие побитовые операции над двумя значениями |
| ceq, cgt, clt | Позволяют сравнивать два значения в стеке различными способами: ceq — сравнение на предмет равенства; cgt — сравнение на предмет того, является ли одно из них больше другого; clt — сравнение на предмет того, является ли одно из них меньше другого |
| box, unbox | Применяются для преобразования ссылочных типов в типы значения и наоборот |
| ret | Применяется для выхода из метода и (если необходимо) возврата значения вызывающему коду |
| beq, bgt, ble, bit, switch | Применяются (вместе с другими похожими кодами операций) для управления логикой ветвления внутри метода: beq — позволяет переходить к определенной метке в коде, если при проверке значения оказываются равными; bgt — позволяет переходить к определенной метке в коде, если при проверке одно из значений оказывается больше другого; ble — позволяет переходить к определенной метке в коде, если при проверке одно из значений оказывается меньше или равным другому; bit — позволяет переходить к определенной метке в коде, если при проверке одно из значений оказывается меньше другого. Все связанные с ветвлением коды операций требуют указания в CIL-коде метки, к которой должен осуществляться переход в случае, если результат проверки оказывается истинным |
| call | Применяется для вызова члена определенного типа |
| newarr, newobj | Позволяют размещать в памяти, соответственно, новый массив или новый объект |
Коды операций следующей обширной категории применяются для загрузки (заталкивания) аргументов в виртуальный стек выполнения. Обратите внимание, что все эти ориентированные на выполнение загрузки коды операций сопровождаются префиксом ld (который означает “load” — загрузка):
| Код операций | Описание |
| ldarg | Позволяет загружать в стек аргумент метода. Помимо основного варианта ldarg (который работает с индексом, представляющим аргумент), существует множество других вариантов. Например, есть варианты ldarg, которые работают с числовым суффиксом (ldarg_0) и позволяют жестко кодировать загружаемый аргумент. Также есть варианты, позволяющие жестко кодировать как только один тип данных, к которому относится аргумент, с помощью константной нотации CIL (например, ldarg_I4 для int32), так и тип данных и значение вместе (например, ldarg_I4_5, позволяющий загружать int32 со значением 5) |
| ldc | Позволяет загружать в стек значение константы |
| ldfld | Позволяет загружать в стек значение поля уровня экземпляра |
| ldloc | Позволяет загружать в стек значение локальной переменной |
| ldobj | Позволяет получать все значения размещаемого в куче объекта и помещать их в стек |
| ldstr | Позволяет загружать в стек строковое значение |
Помимо кодов операций, связанных с загрузкой, в CIL поддерживаются коды операций, которые позволяют явным образом извлекать из стека самое верхнее значение. Как уже было показано в нескольких примерах ранее, извлечение значения из стека обычно подразумевает его сохранение во временном локальном хранилище с целью дальнейшего использования (например, параметра для последующего вызова метода). Из-за этого многие коды операций, которые позволяют извлекать текущее значение из виртуального стека выполнения, сопровождаются префиксом st (от “store” — сохранить); ниже перечислены некоторые наиболее часто используемые из них:
| Код операций | Описание |
| pop | Позволяет удалять значение, которое в текущий момент находится на верхушке стека вычислений, но не сохранять его |
| starg | Позволяет сохранять самое верхнее значение из стека в аргументе метода с определенным индексом |
| stloc | Позволяет извлекать текущее значение из верхушки стека вычислений и сохранять его в списке локальных переменных с определенным индексом |
| stobj | Позволяет копировать значение определенного типа из стека вычислений в память по определенному адресу |
| stsfld | Позволяет заменять значение статического поля значением из стека вычислений |
Следует принимать во внимание, что различные коды операций в CIL могут предусматривать неявное извлечение значений из стека во время решения поставленной перед ними задачи. Например, при вычитании одного числа из другого с использованием кода операции sub должно быть очевидным, что перед собственно вычислением sub должна извлечь из стека два следующих доступных значения. Результат вычисления будет снова помещен в стек.
Директива .maxstack
При написании кода реализации методов непосредственно в CIL необходимо помнить об одной особой директиве, которая называется .maxstack. Эта директива позволяет указать максимальное количество переменных, которое может помещаться в стек в любой момент во время выполнения метода. Директива имеет значение по умолчанию 8, подходящее для подавляющего большинства создаваемых методов. При желании можно вручную вычислять количество локальных переменных в стеке и указывать его явно.