Last Updated:

A closer look at the intermediate language (IL)

Microsoft's intermediate language (IL) obviously plays a fundamental role in the .NET environment. As C# developers, we now understand that before we run, our C# code is compiled into IL. The most important properties of IL can be formulated as follows:

  • Object orientation and application of interfaces.
  • A strong distinction between value types and reference types.
  • Strong data typing.
  • Error handling through the use of exceptions.
  • Use attributes.

Support for object orientation and interfaces


The independence of .NET from language has some practical limitations. Inevitably, IL must implement some specific programming methodology, which means that the source language must also be compatible with that methodology. The principles that guided Microsoft in the creation of IL: classical object-oriented programming with the implementation of single class inheritance. In addition to the classic object orientation, IL also introduces the concept of interfaces that were first implemented under Windows with COM.

One of the problems with interlingual interaction was that components written in different languages had to be debugged independently of each other. It was impossible to switch from one language to another in the debugger. Therefore, by the ability of language interaction, we mean the possibility for classes written in one language to directly access classes written in another language. In particular:

  • A class written in one language can inherit from a class implemented in another.
  • A class can contain an instance of another class, regardless of the languages in which each class is written.
  • An object can call methods of another object written in another language.
  • Objects (or references to objects) can be passed between methods.
  • When you call methods between languages, you can step through calls in the debugger, even if it means that you have to move between pieces of source code written in different languages.

These are all ambitious goals, but surprisingly, thanks to .NET and IL, they have been achieved. If you move between methods in the debugger, this capability is provided by the Visual Studio .NET integrated development environment (ISR) instead of the CLR itself.

Strong data typing


One of the most important aspects of IL is that it is based solely on strong data typing. This means that all variables have a well-defined specific data process. In particular, IL generally does not allow for any actions that result in undefined data types.

Although type security can initially be detrimental to performance, in many cases the benefits derived from services delivered by .NET that rely on type security far outweigh the losses from some performance degradation. These services include the following aspects:

  • The ability of interlingual interaction.
  • Garbage collection.
  • Security.
  • Application domains.

General Type System (CTS)


The issue of data types is solved in .NET through the use of a common type system (CTS). CTS describes the predefined data types that are available in IL, so all .NET-oriented languages generate compiled code that is ultimately based on these types.

CTS describes not just primitive data types, but a rich hierarchy of types that includes well-defined points at which code can define its own types. The hierarchical structure of the general type system (CTS) reflects the object-oriented methodology of single inheritance of IL and is shown in the table.

Table 1. Description of types




The base class that represents the type.

Value Types

A base class that provides any type of value.

Reference types

Any types that are referenced and stored in the heap.

Built-in value types

Include most standard primitive types that represent numbers, Boolean values, or symbols.


Sets of enumerated values.

Custom value types.

Types that are defined in source code and stored as value types. In C# terminology, this means any structure.

Interface types.


Pointer types.


Self-documented types.

Types of data that represent information about themselves that has been leaked to the garbage collector.


Any type that contains an array of objects.

Class types.

Self-documented types, but not arrays.


Types that are designed to store references to methods.

User-defined reference types.

Types defined in source code and stored as reference types. In C# terminology, these are any classes.

Packaged value types.

Types of values that are temporarily placed in references so that they can be stored on the heap.

Common Language Specification (CLS)


The Common Language Specification (CLS) works in conjunction with CTS to provide language interoperability. The CLS is a set of minimum standards that all .NET-oriented compilers must adhere to. Because IL is a very rich language, the developers of most compilers prefer to limit the capabilities of a particular compiler to supporting only a subset of the IL and CTS tools. This is normal as long as the compiler supports everything defined in CTS.

It is perfectly acceptable to write code that is not CLS compliant. However, in this case it is not guaranteed
that the compiled IL code will be fully interoperable.

For example, take case sensitivity. IL is case-sensitive. Developers who write in case-sensitive languages make extensive use of the flexibility that case-sensitiveness provides when choosing variable names. However, Visual Basic 2005 is not case sensitive. The CLS sidesteps this problem by specifying that any CLS-compliant code should not include any name pairs that differ only in case. Therefore, Visual Basic 2005 code can work with CLS-compliant code.

This example illustrates that CLS works in two ways. First, it means that individual compilers are not powerful enough to support all the features of .NET - this presents a challenge for compiler developers of other programming languages that target .NET. Second, it ensures that if you limit classes to clS-compliant tools only, code written in any other programming language can use those classes.

The beauty of this idea is that restrictions on the use of CLS-compliant tools are imposed only on public and protected class members and on public classes. Within the private implementation of your classes, you are free to write any CLS-incompatible code, because code from other assemblies doesn't have access to that part of your code anyway.

We won't dive into the details of the CLS specification here. In general, the CLS doesn't particularly affect your C# code, because there aren't many tools in C# that aren't CLS-compliant.