This chapter provides an overview of .NET 5 (previously .NET Core) and describes the fundamental architectural and the engineering features that you should expect in any implementation of .NET 5 (regardless of hardware, operating system, or execution system).
Acronyms
Base Class Library (BCL)
Common Intermediate Language (CIL)
Common Language Infrastructure (CLI)
Common Language Runtime (CLR)
Common Type System (CTS)
Framework Class Library (FCL) (Although not specific to the .NET Framework implementation, the term is used for the full range of .NET types available in an official distribution of .NET.)
Intermediate Language (IL)
Microsoft Intermediate Language (MSIL)
Virtual Execution System (VES)
Windows Presentation Foundation (WPF) (a.k.a. execution engine)
ECMA-335 and .NET
ECMA-335
The ECMA-335 standard specification defines the Common Language Infrastructure (CLI) , which includes a set of conceptual definitions and rules to be followed and engineering mechanisms to be implemented, independent of the target operating system and hardware platforms. The CLI ensures that applications, components, and libraries can be written in multiple high-level languages and can be executed in different target system environments without needing to be rewritten.
More objectively, the CLI is an open specification that describes executable code and an execution environment that enables multiple high-level languages to be used on different architectural platforms without being rewritten.
Base Class Library (BCL): Foundational library defined by and part of the CLI standard specification. It is implemented by .NET Framework, .NET Core, .NET 5, and .NET 6 (early stages, available on Github.com), and is the main reason for the existence of the .NET standard.
Common Language Specification (CLS): Rules (restrictions and models) required for language interoperability. The detailed information on the CLS group is a subset of what is in the CTS, but the content is primarily for language designers and class library designers (frameworks). So, learning about CTS will offer a great base of knowledge for you and your team for when we start working with the rules in the CLS.
Common Type System (CTS): The CTS is a set of data types and operations that are shared by all languages that support the CTS, and learning about the CTS will offer a great base of knowledge to you and your team when we start working with the rules in the CLS.
Metadata: The metadata describes the program structure, enabling languages and tools to work together. Detailed understanding of the metadata group is not a requisite for a component developer or application developer. Instead, detailed information about such is primarily for tool builders and compiler writers.
Virtual Execution Engine (VES): How code is executed (and how types are instantiated), interacts, and dies. More abstractly, it is also known as an execution engine or execution environment. This execution system is responsible for loading, instantiating, executing, and ensuring the cohesiveness of the interactions between the instances. In brief, it offers entire lifecycle support for the instance of the types. The execution engine understands concepts, architecture, and implementation details of two fundamental areas of the platform: the CTS and the VES.
- Semantics:
Capability to recognize contextuality (semantics), meaning mechanisms to constantly observe your own environment and ways to guarantee advanced security rules, data integrity (acting based on more flexible or disciplined rules), dynamic extensibility and expandability. In addition, we have the capability to interact with highly specialized environments (advanced data management systems, for example), development software environment systems (for instance, Microsoft Visual Studio), different target operating systems and hardware platforms (for example, the Microsoft Windows operating system implementations and UNIX-based operating system implementations, including Linux distributions, Apple MacOS, Apple iOS, Google Android, FreeBSD, IBM AIX, Red Hat Linux, Intel x86/Intel x64, ARM 32-bit, ARM 64-bit, IoT high-specialized environment for embedded systems, web development, desktop development, mobile development, game development, artificial intelligence development, machine-learning development, quantum computing environments, supercomputing highly specialized environments, scientific highly specialized research and development environments, research and development for enterprise and government at any level of complexity [local to global], and many more).
Capable of hosting, and be hosted by, other environments (such as Microsoft SQL Server advanced data management system, Microsoft Visual Studio 2017, Microsoft Visual Studio 2019, and the Microsoft Azure set of advanced cloud products and services).
Intermediate Language: An IL is an abstract language used by a compiler as a step between program code and assembly code.
CIL: The CIL is a formal instruction set to the CIL described in the CLI standard specification.
Microsoft Intermediate Language (MSIL) : MSIL is Microsoft’s implementation of the formal instruction set based on the ECMA-335 CIL described in the CLI standard specification.
When writing code using a programming language that adheres to the CLI standard specification, the result of the compiled code is a sequence of instructions of the CIL instruction set, as examples show in Listing 1-1 and Listing 1-2.
Open the sample solution RVJ.Core.sln at <install_dir_on_your_local_computer>SourcesAPIsDotNET5.0ProCustomLibsCh01RVJ.Core.
In the C# programming language, because C# treats System.Object as the base class, we do not need to use the System.Object root data type explicitly when we do not have another class as the base data type.
Typical Source Code in the C# Programming Language for a Console Application with an Entry-Point Member Method Called Program.Main()
Source Code in MSIL Generated in the Binary File, .EXE, or .DLL
These instructions are not for real hardware or processors. Instead, the CLI standard specification describes a virtual environment that includes some characteristics and functionalities of the elements available in a real computer.
.NET Platform
Microsoft .NET is the official commercial name for the group of technologies and tools designed and implemented based on what is in the ECMA-335 standard specification.
Common Language Runtime, as the name suggests, is an implementation based on the CLI standard specification, and an implementation of the CLR has a set of elements for a fundamental architectural model. Each element has a fundamental set of conceptual definitions and rules to be followed, and engineering mechanisms to be implemented, independently of the target operating system and hardware platforms.
When we are implementing a CLR environment and technologies of a .NET platform, we are creating software elements for a platform that is a nonspecific hardware-based computer (more specifically, a software-only computer, and more commonly known as a virtual computer ). This description includes when planning and implementing custom data types, custom components, custom controls, custom libraries, and specialized tools and frameworks.
For this text, we are using a .NET 5 implementation of the CLR for the sample projects and respective source code.
You can check for the most up-to-date versions of .NET 5 at the official Microsoft website:
https://dotnet.microsoft.com/download/dotnet/5.0.
Independently or together, these abstract aspects focus on management of data types. So, reasonably, that form of environment and its components is known as a managed environment .
C#
MSIL
For example, when we are developing some application and choose the System.String reference type, we are using one of the fundamental types available through the BCL.
However, the string reference type exists only because the CTS has the string fundamental built-in type defined on it, which is one of the platform-specific fundamental built-in types upon which string operations are built. In fact, the string content (value of) in any instance is made up of a sequence of values of the CTS char platform fundamental built-in type, which is System.Char fundamental data type in the BCL. These platform fundamental built-in types, BCL fundamental types, and any other types derived or based on them follow the rules described by the unified type system.
In the CLI specification, this unified type system is the CTS, which describes rules about conceptual, structural, and behavioral elements that must be followed by the CLI itself and specialized tools (such as compilers and runtime environments).
Fundamental Types Defined Through CTS
BCL Types | CTS Types |
---|---|
C# | CIL/MSIL |
System.Boolean | bool |
System.Char | char |
System.Object | object |
System.String | string |
System.Single | float32 |
System.Double | float64 |
System.SByte | int8 |
System.Int16 | int16 |
System.Int32 | int32 |
System.Int64 | int64 |
System.IntPtr | native int |
System.UIntPtr | native unsigned int |
System.TypedReference | typedref |
System.Byte | unsigned uint8 |
System.UInt16 | unsigned uint16 |
System.UInt32 | unsigned uint32 |
System.UInt64 | unsigned uint64 |
Fundamental Data Types
Numeric Data Type | Description |
---|---|
Byte unsigned integer | All bits used to represent the value. Values range from 0 to 255. (2^8-1) |
Word unsigned integer | All bits used to represent the value. Values range from 0 to 65,535. (2^16-1) |
Doubleword unsigned integer | All bits used to represent the value. Values range from 0 to 4,294,967,295. (2^32-1) |
Quadword unsigned integer | All bits used to represent the value. Values range from 0 to 18,446,744,073,709,551,615. (2^64-1) |
Byte signed integer | The first 7 bits (6…0) used to represent the value, the most significant bit (MSB) used as the signed bit. When the MSB has value 0, the number is positive. When the MSB has value 1, the number is negative. Values range from -128 to +127. |
Word signed integer | The first 15 bits (14…0) used to represent the value, he MSB used as the signed bit . When the MSB has value 0, the number is positive. When the MSB has value 1, the number is negative. Values range from -32,768 to +32,767. |
Doubleword signed integer | The first 31 bits (30…0) used to represent the value, the MSB used as the signed bit. When the MSB has value 0, the number is positive. When the MSB has value 1, the number is negative. Values range from -2^31 to +2^31-1. |
Quadword signed integer | The first 63 bits (62…0) used to represent the value, the MSB used as the signed bit When the MSB has value 0, the number is positive. When the MSB has value 1, the number is negative. Values range from -2^63 to +2^63-1. |
CTS System.Object (Root Managed Object Type)
BCL Types | CTS Types | |
C++/CLI projection | C# programming language | CIL |
System::Object^ (same root managed object type) | C# object is the keyword used for CTS/BCL System.Object (same root managed object type) | object (same root managed object type) |
Contextual Resources and Their Fundamental Purposes
Your .NET specialized applications | Applications, services, components, libraries, and frameworks. |
.NET | Software development kit (SDK, a specialized tools for software development, analysis, deployment, and some types of management) Specialized components, libraries, and frameworks |
CLR | Implementation of a specialized managed environment based of CLI specification Uses the resources of the underlying hardware and operating system platform (for example, Microsoft Windows operating system) Adaptable and capable of using the specialized resources of the underlying hardware and operating system (for example, Microsoft Windows 10, Microsoft Windows Server 2016, Linux distributions, Apple iOS, and Apple MacOS. |
Remember that this is not a one-to-one mapping between reserved words, data structures, specialized resources, or anything else in the programming languages. That is, what is formalized through the instructions in CIL, what is defined in the CLI specification, and what is implemented by the mechanisms on the platform is what prevails.
cil is a code implementation attribute that specifies that the method declaration and implementation consist only of CIL code (that is, managed code).
native is a code implementation attribute that specifies that the method declaration and implementation consist only of native code (that is, native instructions of a specific hardware/processor platform). Currently, this functionality of the managed environment CLR implementation is used specifically as one of the base technologies of Platform Invoke (P/Invoke). P/Invoke is one of the mechanisms of the platform, and it is described in the CLI specification.
runtime is a code implementation attribute that specifies that the implementation of the method be provided automatically by the runtime.
managed is a code implementation attribute that is used with methods for which implementation is written using only CIL code.
unmanaged is a code implementation attribute that is used to describes that the implementation is not external. Currently, this code implementation attribute is used by P/Invoke technology, but it is not restricted to just that use.
cil
native
runtime
managed
unmanaged
When unmanaged code needs to be used from the managed code, the unmanaged code implementation attribute must be applied on the method implementation. In the specific case of the P/Invoke mechanism, the use of the unmanaged code implementation attribute is required.
The pinvokeimpl method attribute is used to indicate that the runtime will switch from a managed state to an unmanaged state when executing the unmanaged code.
Listing 1-3 shows an example of a managed code implementation that uses an unmanaged code implementation of a well-known Windows application programming interface (API) HeapAlloc() function . The method has been applied the unmanaged and native code implementation attributes.
Excerpt in MSIL of Unmanaged Code (Using P/Invoke to Call the HeapAlloc() Function of Windows Memory Management, the Windows API)
At this point, we have the following sequence of elements: the CLI standard specification that is composed by and describes the CTS group, the metadata group, the CLS and VES group, and the CLI itself.
About the Common Type System
When working with a sequence of bits, it is necessary to define the organization of these bits to do something useful. So, the data signified by the bit pattern should identify the data type (or a contextualized type based on the data).
The data type must have a purpose and contextually well-defined characteristics. For example, with regard to structural terms, the data type must have the required number of bits as defined and the fundamental operations that the type supports.
A type’s conceptual, structural, and behavioral fundamental characteristics create a model as to what can be done and what cannot be done with any particular type: a type system model. Because the number of types is constantly increasing, a type system model is necessary to enforce rules to ensure that the environment works as designed and expected.
A type system model describes the necessary rules related to each type’s conceptual, structural, and behavioral characteristics.
Fundamental Types and Hardware Platform
For this discussion, we use Intel IA-32/x64 and Intel 64 fundamental built-in data types (or fundamental built-in types), and we use some defined assembly instructions (implemented and supported) that derive the hardware architecture and the contextual interpretation of the bits on the data type.
The fundamental built-in data types are those defined as integral elements of the platform (in this case, the Intel IA-32/x64 and Intel 64 processor hardware architecture). Therefore, these types are integral elements of the hardware architecture and are not defined by an external library or execution environment.
Byte (8 bits) (1 byte)
Word (16 bits) (2 bytes)
Doubleword (32-bits) (4 bytes)
Quadword (64 bits) (8 bytes)
Double quadword (128 bits) (16 bytes)
Although these fundamental built-in data types are supported by a common set of assembly instructions (such as MOV) that perform a common set of operations such move data from one place to another, some assembly instructions support additional interpretation of fundamental built-in data types.
The purpose of this additional interpretation is to allow numeric operations to be performed, and within this context these fundamental built-in data types are viewed and manipulated as numeric data types.
The Intel IA-32/x64 and Intel 64 processors recognize two integer types: signed and unsigned.
Assembly instructions such as ADD and SUB can perform operations on both signed integers and unsigned integers, but some assembly instructions can perform operations only with one type.
The Organization of Fundamental Data Types
- Byte (8 bits)
Bits 7…0
- Word (16 bits)
Bits 15…0
Bits 15…8 (high byte)
Bits 7…0 (low byte)
- Doubleword (32 bits)
Bits 31…0
Bits 31…16 (high word)
Bits 15…0 (low word)
- Quadword (64 bits)
Bits 63…0
Bits 63…32 (high doubleword)
Bits 31…0 (low doubleword)
- Double quadword (128 bits)
Bits 127…0
Bits 127…64 (high quadword)
Bits 63…0 (low quadword)
Table 1-2 describes the bits in more detail, including information about fundamental hardware requirements and integer types (signed and unsigned).
CTS for Fundamental Types
The CTS supports types that describe values and types that specify contracts (behaviors that the type supports), and the support for these types must be present in an implementation of a CLR. These two types are supported because one of the principles of the CTS is to support object-oriented programming (OOP), procedural, and functional programming languages.
A value is a bit pattern used to represent types such as numbers (for example, integer numbers and float-pointing numbers).
C# Examples Declaring Variables Using uint and System.UInt32, the Same Kind of Object (An Instance of the Value Type of System.UInt32 Data Type of BCL)
A value type is not an object type, but it is defined using a class definition (declaration and implementation).
Remember that this way of work is defined by CTS and supported by VES in the CLR. From the perspective of the type system and execution environment, it is necessary that an object be declared, defined, and implemented to work within the CLR.
Table 1-3 describes the fundamental built-in types defined by CTS. As the table shows, the root object type is accessible through the object keyword of the CIL. So that programming languages such as C#, C++/CLI projection, F#, VB.NET, and others can access this root object type of the platform, there is a library of fundamental types that is part of the CLI specification. This foundational library is the BCL.
This root object type is the System.Object reference type. When declaring a variable of the object type (CTS model definition) or System.Object (BCL) reference type using any high-level programming language such as C#, C++/CLI projection, F#, VB.NET, and so on, the compiler generates an intermediate code using the object keyword of the CIL. Table 1-4 summarizes and helps you understand and memorize this sequence in a straightforward way.
Virtual Execution System
The VES provides an environment for running managed code, security boundaries, and memory management.
Two fundamental built-in types (string and array) are used as a starting point in this discussion to explain various aspects of CTS and VES.
These platform built-in fundamental types are present in any kind of software, so they stand as orthogonal elements.
However, the .NET platform also has a special foundational library, also part of the CLI specification, that supplies specialized types necessary to design and implement any kind of software: the BCL.
As we explore the the organization of the BCL, we’ll use the System.Object, System.String, and System.Array reference types as starting points and deconstruct many aspects of their implementation. This discussion will then enable us to explore the interface types implemented by these types in various specialized frameworks (such as Windows Forms, Windows Presentation Foundation [WPF], Universal Windows Platform [UWP] applications, and ASP.NET).
The VES provides direct support for a set of platform-specific built-in fundamental types, defines a hypothetical machine with an associated machine model and state, and provides a set of control flow constructs and an exception-handling model.
To a considerable extent, the purpose of the VES is to provide the support required to execute the MSIL instruction set.
The VES is the system that implements and enforces the CTS model. For example, the VES is responsible for loading and running programs written to CLI.
The VES provides the services needed to execute managed code and data using the metadata to connect separately generated modules together at runtime. The VES is also known as the execution engine.
.NET Module
When we use C++ to write code, the result of the compiled and linked code is a binary file in a specific format. In this case, we are working with PE/COFF (Portable Executable / Common Object File Format), which is used by the Microsoft Windows operating system. When we use C# to write code, or when we use any other programming language or group of extensions that adhere to the CLI specification, the resulting binary file is in the same PE/COFF format. However, that resulting binary file has some data structures changed/included to support the requirements described by CLI specification and aspects of the Microsoft Windows operating system. This is called the CLI PE/COFF module.
Currently, on Microsoft Windows, the CLI PE/COFF module can have .EXE, .DLL, .netmodule, .WinMD, and .UWP extensions created and recognized by the operation system or development tools. In addition, it can have any other extension that can be registered and recognized by the operating system or specialized tools (for software development or not).
In fact, the use of an extension is not required, but it is a good practice and the accepted standard.
If we are using .NET 5 or .NET Core (not the old Windows-only .NET Framework) in a different operating system and on a different hardware platform, the extensions and file formats used are specific to such software and hardware environments. However, the fundamental structural resources defined in CLI as a starting point are the same.
One VES responsibility is to load the CLI PE/COFF modules. Doing so includes verifying some structural rules about the file format and guaranteeing that all information is as expected. The VES uses the metadata information in the CLI PE/COFF modules to verify that the structural aspects are recognized by the rules that it knows as valid, required, or optional. If the structural elements exist and are valid, the next step is to apply the rules based on the nature of the elements and the context of use.
For example, if the element is a managed type, the execution system needs to verify whether it is a value type or a reference type.
If the element is an assembly reference type, one responsibility of this type is to describe various characteristics of the managed module (structural and behavioral), such as the relationships it has with other managed modules and what managed types are in it (and in any other managed module).
.NET Assemblies
People often wonder what a .NET assembly is exactly. Put simply, and as defined and described by the CLI, an assembly is a logical unit for management and deployment of resources designed to work together. In an implementation of CLR, assemblies can be static or dynamic.
Static Assemblies
- Assembly mscorlib
Module mscorlib.dll
Module System.Runtime.dll
Module netstandard.dll
- Assembly System.Activities (part of Microsoft Windows Workflow Foundation)
Module System.Activities.dll
- Assembly System.Diagnostics.Debug
Module System.Diagnostics.Debug.dll
Module System.dll
Module netstandard.dll
Dynamic Assemblies
Dynamic assemblies are created dynamically at runtime and are created via specialized API calls of .NET 5/Core. These dynamic assemblies are created and executed directly in memory. However, the dynamic assembly can be saved in a storage device, but only after being executed.
In a typical project, though, we have many files—binary files with executable code or binary files with other types of data (for example, images)—that are part of the software. Therefore, the description, verification, and reinforcement of the relations and dependencies among them are made in part by the metadata.
Metadata is partly responsible for making resources available to perform these tasks.
Working with Assemblies and Modules
For a static assembly or a dynamic assembly, the principles prevails, a way of keep the cohesiveness of the types and resources designed to work together. Deployment, Execution and Management. The information stored in the modules and created through assemblies is what helps the runtime environment understand and apply the rules to the relations among the elements.
Let’s use a typical static assembly.
CIL that implements all the types and required logic to the module
Metadata
The resources (audio/video files, localization support files, images and custom files created specifically for the application)
The assembly manifest
From the perspective of the runtime environment and basic structural rules described in the CLI, of these four elements, only the assembly manifest is a required item. However, considering even the simplest application or component, if we do not have the other elements, the application or component does not have a practical use (except for learning about the assemblies and modules, which I consider a quite practical use).
Organization of Elements in a Module (Physical File)
We start with a basic example here and continue with more details in Chapter 2.
- 1.
Using the code editor of your preference, create a simple file and save it with the name RVJ.ProDotNETCustomLibs.il in the directory of your choice that can be used to build source code.
- 2.
Open (as administrator) one of the developer command prompts installed and configured by Microsoft Visual Studio 2019.
- 3.
Copy the following sequence of MSIL code into the file RVJ.ProDotNETCustomLibs.il and save the file:
- 4.
In the developer command prompt, write the following command:
If the code compiles without error, the output will be a binary file with the name RVJ.ProDotNETCustomLibs.dll.
By following these steps, we have created a single-file static assembly, with only the assembly manifest.
Using the ILDASM Tool
Implementing the entrypoint Method
As you can see, the name of the .entrypoint method does not need to be main.
Fundamental Keywords Used by Static Assemblies or Dynamic Assemblies
As you can see, the VES handles a lot of work. Even still, though, there are more interesting functionalities within this mechanism.
Chapter 2 discusses these resources and goes into more detail about the CTS and VES. Specifically, you’ll read more about fundamental built-in types and about how the execution environment deals with these types and structural elements of the platform. Initially, we use code written directly in CIL to provide more information about the use of the types and so that you better understand how to work with modules and assemblies. We then use some code in C++ to highlight some internal aspects of the execution environment and some special types. From that point, we embark on our journey through foundational BCL using the MSIL and C# programming languages.