Understanding the CLI
The CLI is a specification that describes how a runtime environment can be used on different computer platforms without being rewritten for specific architectures. It is developed by Microsoft and standardized by ECMA and ISO. The following diagram shows the high-level functionality of the CLI:

Figure 1.1 – Diagram of the high-level functionality of the CLI
The CLI enables programs written in a variety of programming languages (that are CLS-compliant) to be executed on any operating system and with a single runtime. The CLI specifies a common language, called the Common Language Specification (CLS), a common set of data types that any language must support, called the Common Type System, and other things such as how exceptions are handled and how the state is managed. The various aspects specified by the CLI are described in more detail in the following sections.
Information box
Because of the limited scope of this chapter, a deep dive into the specification is not possible. If you want more information about the CLI, you can visit the ISO site at https://www.iso.org/standard/58046.html.
There are several implementations of the CLI and among these, the most important ones are .NET Framework, .NET Core, and Mono/Xamarin.
Common Type System (CTS)
The CTS is a component of the CLI that describes how type definitions and values are represented and memory is intended to facilitate the sharing of data between programming languages. The following are some of the characteristics and functions of the CTS:
- It enables cross-platform integration, type safety, and high-performance code execution.
- It provides an object-oriented model that supports the complete implementation of many programming languages.
- It provides rules for languages to ensure that objects and data types of objects written in different programming languages can interact with each other.
- It defines rules for type visibility and access to members.
- It defines rules for type inheritance, virtual methods, and object lifetime.
The CTS supports two categories of types:
- Value types: These contain their data directly and have copy semantics, which means when an object of such a type is copied its data is copied.
- Reference types: These contain references to the memory address where the data is stored. When an object of a reference type is copied, the reference is copied and not the data it points to.
Although it is an implementation detail, value types are usually stored on the stack and reference types on the heap. Conversion between value types and a reference type is possible and known as boxing, while the other way around is called unboxing. These concepts will be explained in further detail in the next chapter.
Common Language Specification (CLS)
The CLS comprises a set of rules that any language that targets the CLI needs to adhere to, to be able to interoperate with other CLS-compliant languages. CLS rules fall into the broader rules of the CTS and therefore it can be said that the CLS is a subset of CTS. All of the rules of CTS apply to the CLS unless the CLS rules are stricter. Language constructs that make it impossible to easily verify the type safety of the code were excluded from the CLS so that all languages that work with the CLS can produce verifiable code.
The relationship between the CTS and CLS as well as the programming languages targeting the CLI is conceptually shown in the following diagram:

Figure 1.2 – A diagram showing the conceptual relationship between the CTS and CLS and the programming languages that target the CLI
Components built using only the rules of the CLS are called CLS-compliant. An example of such components is the framework libraries that need to work across all of the languages supported on .NET.
Common Intermediate Language (CIL)
CIL is a platform-neutral intermediate language (formerly called Microsoft Intermediate Language or MSIL) that represents the intermediate language binary instruction set defined by the CLI. It is a stack-based object-oriented assembly language that represents the code in byte-code format.
Once the source code of an application is compiled, the compiler translates it into the CIL bytecode and produces a CLI assembly. When the CLI assembly is executed, the bytecode is passed through the Just-In-Time compiler to generate native code, which is then executed by the computer's processor. The CPU and the platform-independent nature of the CIL make it possible that the code is executed on any environment supporting the CLI.
To help us to understand the CIL, let's look at an example. The following listing shows a very simple C# program that prints a Hello, World!
message to the console:
using System; namespace chapter_01 { class Program { static void Main(string[] args) { Console.WriteLine("Hello World!"); } } }
It is possible to view the content of the assembly produced by the compiler using various utility tools, such as ildasm.exe
, which comes with .NET Framework, or ILSpy, which is an open source .NET assembly browser and decompiler (available at http://www.ilspy.net/). The ildasm.exe
file shows a visual representation of the program and its components, such as classes and members:

Figure 1.3 – A screenshot of the ildasm tool showing the content of an assembly
You can also see the content of the manifest (which includes assembly metadata) as well as the CIL code for each method if you double-click on it. The following screenshot shows the disassembled code of the Main
method:

Figure 1.4 – A screenshot of the ildasm tool showing the IL code of the Main method
A human-readable dump of the CIL code is also available. This starts with the manifest and continues with the class member's declarations. A partial listing of the CIL code for the preceding program is shown here:
// Metadata version: v4.0.30319 .assembly extern System.Runtime { .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....: .ver 4:2:1:0 } .assembly extern System.Console { .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....: .ver 4:1:1:0 } .assembly chapter_01 { } .module chapter_01.dll // MVID: {1CFF5587-0C75-4C14-9BE5-1605F27AE750} .imagebase 0x00400000 .file alignment 0x00000200 .stackreserve 0x00100000 .subsystem 0x0003 // WINDOWS_CUI .corflags 0x00000001 // ILONLY // Image base: 0x00F30000 // =============== CLASS MEMBERS DECLARATION =================== .class private auto ansi beforefieldinit chapter_01.Program extends [System.Runtime]System.Object { .method private hidebysig static void Main(string[] args) cil managed { .entrypoint // Code size 13 (0xd) .maxstack 8 IL_0000: nop IL_0001: ldstr "Hello World!" IL_0006: call void [System.Console]System.Console::WriteLine(string) IL_000b: nop IL_000c: ret } // end of method Program::Main .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { // Code size 8 (0x8) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [System.Runtime]System.Object::.ctor() IL_0006: nop IL_0007: ret } // end of method Program::.ctor } // end of class chapter_01.Program
An explanation of the code here is beyond the scope of this chapter, but you can probably identify at a glance parts of it such as classes, methods, and instructions executed in each method.
Virtual Execution System (VES)
VES is a part of the CLI that represents a runtime system that provides the environment for executing the managed code. It has several built-in services to support the execution of code and handling of exceptions, among other things.
The Common Language Runtime is .NET Framework's implementation of the Virtual Execution System. Other implementations of the CLI provide their own VES implementations.