The Common Language Infrastructure - Essential C# 6.0 (2016)

Essential C# 6.0 (2016)

21. The Common Language Infrastructure

One of the first items that C# programmers encounter beyond the syntax is the context under which a C# program executes. This chapter discusses the underpinnings of how C# handles memory allocation and de-allocation, type checking, interoperability with other languages, cross-platform execution, and support for programming metadata. In other words, this chapter investigates the Common Language Infrastructure (CLI) on which C# relies both at compile time and during execution. It covers the execution engine that governs a C# program at runtime and considers how C# fits into a broader set of languages that are governed by the same execution engine. Because of C#’s close ties with this infrastructure, most of the features that come with the infrastructure are made available to C#.

Image

Defining the Common Language Infrastructure

Instead of generating instructions that a processor can interpret directly, the C# compiler generates instructions in an intermediate language, the Common Intermediate Language (CIL). A second compilation step occurs, generally at execution time, converting the CIL to machine code that the processor can understand. Conversion to machine code is still not sufficient for code execution, however. It is also necessary for a C# program to execute under the context of an agent. The agent responsible for managing the execution of a C# program is the Virtual Execution System (VES), generally more casually referred to as the runtime. (Note that the runtime in this context does not refer to a time, such as execution time; rather, the runtime—the Virtual Execution System—is an agent responsible for managing the execution of a C# program.) The runtime is responsible for loading and running programs and providing additional services (security, garbage collection, and so on) to the program as it executes.

The specifications for the CIL and the runtime are contained within an international standard known as the Common Language Infrastructure (CLI). The CLI is a key specification for understanding the context in which a C# program executes and how it can seamlessly interact with other programs and libraries, even when they are written in other languages. Note that the CLI does not prescribe the implementation for the standard, but rather identifies the requirements for how a CLI platform should behave once it conforms to the standard. This provides CLI implementers with the flexibility to innovate where necessary, while still providing enough structure that programs created by one platform can execute on a different CLI implementation, and even on a different operating system.


Note

Note the similarity between the CIL and CLI acronyms and the names they stand for. Distinguishing between them now will help you avoid confusion later.


Contained within the CLI standard are specifications for the following:

• The Virtual Execution System

• The Common Intermediate Language

• The Common Type System

• The Common Language Specification

• Metadata

• The framework

This chapter broadens your view of C# to include the CLI, which is critical to how C# programs operate and interact with programs and with the operating system.

CLI Implementations

There are several commonly used implementations of the CLI, and a number of implementations now of historical interest. Each implementation of the CLI includes a C# compiler and a set of framework class libraries; the version of C# supported by each, as well as the exact set of classes in the libraries, vary considerably. Table 21.1 on the next page describes these implementations.

Image

Image

TABLE 21.1: Primary C# Compilers

C# Compilation to Machine Code

The HelloWorld program listing in Chapter 1 is obviously C# code, and you compiled it for execution using the C# compiler. However, the processor still cannot directly interpret compiled C# code. An additional compilation step is required to convert the result of C# compilation into machine code. Furthermore, the execution requires the involvement of an agent that adds more services to the C# program—services that it was not necessary to code for explicitly.

All computer languages define syntax and semantics for programming. Since languages such as C and C++ compile to machine code, the platform for these languages is the underlying operating system and machine instruction set, be it Microsoft Windows, Linux, UNIX, or something else. In contrast, with languages such as C#, the underlying platform is the runtime (or VES).

CIL is what the C# compiler produces after compiling. It is termed a “common intermediate language” because an additional step is required to transform the CIL into something that processors can understand. Figure 21.1 on the next page shows the process.

Image

FIGURE 21.1: Compiling C# to Machine Code

In other words, C# compilation requires two steps:

1. Conversion from C# to CIL by the C# compiler

2. Conversion from CIL to instructions that the processor can execute

The runtime is able to understand CIL statements and compile them to machine code. Generally, a component within the runtime performs this compilation from CIL to machine code. This component is the just-in-time (JIT) compiler, and jitting can occur when the program is installed or executed. Most CLI implementations favor execution-time compilation of the CIL, but the CLI does not specify when the compilation needs to occur. In fact, the CLI even allows the CIL to be interpreted rather than compiled, similar to the way many scripting languages work. In addition, .NET includes a tool called NGEN that enables compilation to machine code prior to actually running the program. This preexecution-time compilation needs to take place on the computer on which the program will be executing because it will evaluate the machine characteristics (processor, memory, and so on) as part of its effort to generate more efficient code. The advantage of using NGEN at installation (or at any time prior to execution) is that you can reduce the need for the jitter to run at startup, thereby decreasing startup time.

As of Visual Studio 2015, the C# compiler also supports “.NET native” compilation, whereby the C# code is compiled into native machine code when creating a deployed version of the application, much like using the NGEN tool. Windows Universal applications make use of this feature.

Runtime

Even after the runtime converts the CIL code to machine code and starts to execute it, it continues to maintain control of the execution. The code that executes under the context of an agent such as the runtime is managed code, and the process of executing under control of the runtime ismanaged execution. The control over execution transfers to the data; this makes it managed data because memory for the data is automatically allocated and de-allocated by the runtime.

Somewhat inconsistently, the term Common Language Runtime is not technically a generic term that is part of the CLI. Rather, CLR is the Microsoft-specific implementation of the runtime for the .NET platform. Regardless, CLR is casually used as a generic term for runtime, and the technically accurate term, Virtual Execution System, is seldom used outside the context of the CLI specification.

Because an agent controls program execution, it is possible to inject additional services into a program, even though programmers did not explicitly code for them. Managed code, therefore, provides information to allow these services to be attached. Among other items, managed code enables the location of metadata about a type member, exception handling, access to security information, and the capability to walk the stack. The remainder of this section includes a description of some additional services made available via the runtime and managed execution. The CLI does not explicitly require all of them, but the established CLI platforms have an implementation of each.

Garbage Collection

Garbage collection is the process of automatically de-allocating memory based on the program’s needs. It represents a significant programming problem for languages that don’t have an automated system for performing this cleanup. Without the garbage collector, programmers must remember to always free any memory allocations they make. Forgetting to do so, or doing so repeatedly for the same memory allocation, introduces memory leaks or corruption into the program—something exacerbated by long-running programs such as web servers. Because of the runtime’s built-in support for garbage collection, programmers targeting runtime execution can focus on adding program features rather than “plumbing” related to memory management.


Language Contrast: C++—Deterministic Destruction

The exact mechanics for how the garbage collector works are not part of the CLI specification; therefore, each implementation can take a slightly different approach. (In fact, garbage collection is one item not explicitly required by the CLI.) One key concept with which C++ programmers may need to become familiar is the notion that garbage-collected objects are not necessarily collected deterministically (at well-defined, compile-time–known locations). In fact, objects can be garbage-collected anytime between when they are last accessed and when the program shuts down. This includes collection prior to falling out of scope, or waiting until well after an object instance is accessible by the code.


The garbage collector takes responsibility only for handling memory management; that is, it does not provide an automated system for managing resources unrelated to memory. Therefore, if an explicit action to free a resource (other than memory) is required, programmers using that resource should utilize special CLI-compatible programming patterns that will aid in the cleanup of those resources (see Chapter 9).

Garbage Collection on .NET

Most implementations of the CLI use a generational, compacting, mark-and-sweep–based algorithm to reclaim memory. It is “generational” because objects that have lived for only a short period will be cleaned up sooner than objects that have already survived garbage collection sweeps because they were still in use. This conforms to the general pattern of memory allocation that objects that have been around longer will continue to outlive objects that have only recently been instantiated.

Additionally, the .NET garbage collector uses a mark-and-sweep algorithm. During each garbage collection execution, it marks objects that are to be de-allocated and compacts together the objects that remain so that there is no “dirty” space between them. The use of compression to fill in the space left by de-allocated objects often results in faster instantiation of new objects (than is possible with unmanaged code), because it is not necessary to search through memory to locate space for a new allocation. This also decreases the chance of paging because more objects are located in the same page, which improves performance as well.

The garbage collector takes into consideration the resources on the machine and the demand on those resources at execution time. For example, if memory on the computer is still largely untapped, the garbage collector is less likely to run and take time to clean up those resources. This is an optimization rarely taken by platforms and languages that are not based on garbage collection.

Type Safety

One of the key advantages the runtime offers is checking conversions between types, known as type checking. Via type checking, the runtime prevents programmers from unintentionally introducing invalid casts that can lead to buffer overrun vulnerabilities. Such vulnerabilities are one of the most common means of breaking into a computer system, and having the runtime automatically prevent these holes from opening up is a significant gain.1 Type checking provided by the runtime ensures the following:

1. Assuming you are not the unscrupulous type who is looking for such vulnerabilities.

• Both the variables and the data that the variables refer to are typed, and the type of the variable is compatible with the type of the data to which it refers.

• It is possible to locally analyze a type (without analyzing all of the code in which the type is used) to determine which permissions will be required to execute that type’s members.

• Each type has a compile-time–defined set of methods and the data they contain. The runtime enforces rules about which classes can access those methods and data. Methods marked as “private,” for example, are accessible only by the containing type.


Advanced Topic: Circumventing Encapsulation and Access Modifiers

Given appropriate permissions, it is possible to circumvent encapsulation and access modifiers via a mechanism known as reflection. Reflection provides late binding by enabling support for browsing through a type’s members, looking up the names of particular constructs within an object’s metadata, and invoking the type’s members.


Code Access Security

The runtime can make security checks as the program executes, allowing and disallowing the specific types of operations depending on permissions. Permission to execute a specific function is not restricted to authentication of the user running the program. The runtime also controls execution based on who created the program and whether she is a trusted provider. Similarly, you might want to note that code access security (CAS) also applies the security policy based on the location of the code—by default, code installed on the local machine is more trusted than code from the LAN, which is much more trusted than code on the Internet. Permissions can be tuned such that partially trusted providers can read and write files from controlled locations on the disk, but are prevented from accessing other locations (such as email addresses from an email program) for which the provider has not been granted permission. Identification of a provider is handled by certificates that are embedded into the program when the provider compiles the code.

Platform Portability

One theoretical feature of the runtime is the opportunity it provides for C# code and the resultant programs to be platform-portable—that is, capable of running on multiple operating systems and executing on different CLI implementations. Portability in this context is not limited to the source code such that recompiling is necessary. Rather, a single CLI module compiled for one platform should run on any CLI-compatible platform without needing to be recompiled. This portability occurs because the work of porting the code lies in the hands of the runtime implementation rather than the application developer.

The restriction is, of course, that no platform-specific APIs are used. Because of this restriction, many developers forgo CLI platform-neutral code in favor of accessing the underlying platform functionality, rather than writing it all from scratch.

Historically, it has been quite difficult to write a library of C# code that can be used on multiple platforms because the framework class libraries on each platform have all had different classes available (or different methods in those classes). If you wish to write your core application logic once and ensure that it can be used in any modern implementation of .NET, the easiest way to do so is to create a Portable Class Library project (available as a project type in Visual Studio since 2012). Visual Studio will ensure that any code in the library can reference only classes and methods common to your choice of Windows Desktop, Silverlight, Windows Phone, iOS, Android, and other platform class libraries. Alternatively, cross platform support is available in .NET Core—the long term strategic direction for on which ASP.NET 5 is built.

To create a full graphical application that can run on Windows Desktop, mobile, and console platforms, select one of the “Universal Application” project types in Visual Studio 2015.

Performance

Many programmers accustomed to writing unmanaged code will correctly point out that managed environments impose overhead on applications, no matter how simple they are. The trade-off is one of increased development productivity and reduced bugs in managed code versus runtime performance. The same dichotomy emerged as programming went from assembler to higher-level languages such as C, and from structured programming to object-oriented development. In the vast majority of scenarios, development productivity wins out, especially as the speed and reduced price of hardware surpass the demands of applications. Time spent on architectural design is much more likely to yield big performance gains than the complexities of a low-level development platform. In the climate of security holes caused by buffer overruns, managed execution is even more compelling.

Undoubtedly, certain development scenarios (device drivers, for example) may not yet fit with managed execution. However, as managed execution increases in capability and sophistication, many of these performance considerations will likely vanish. Unmanaged execution will then be reserved for development where precise control or circumvention of the runtime is deemed necessary.2

2. Indeed, Microsoft has indicated that managed development will be the predominant means of writing applications for its Windows platform in the future, even for those applications that are integrated with the operating system.

Furthermore, the runtime introduces several factors that can contribute to improved performance over native compilation. For example, because translation to machine code takes place on the destination machine, the resultant compiled code matches the processor and memory layout of that machine, resulting in performance gains generally not leveraged by nonjitted languages. Also, the runtime is able to respond to execution conditions that direct compilation to machine code rarely takes into account. If, for example, the box has more memory than is required, unmanaged languages will still de-allocate their memory at deterministic, compile-time–defined execution points in the code. Alternatively, jit-compiled languages will need to de-allocate memory only when it is running low or when the program is shutting down. Even though jitting can add a compile step to the execution process, code efficiencies that a jitter can insert may lead to improved performance rivaling that of programs compiled directly to machine code. Ultimately, CLI programs are not necessarily faster than non-CLI programs, but their performance is competitive.

Application Domains

By introducing a layer between the program and the operating system, it is possible to implement virtual processes or applications known as application domains (app domains). An application domain behaves like an operating system process in that it offers a level of isolation between other application domains. For example, an app domain has its own virtual memory allocation, and communication between app domains requires distributed communication paradigms, just as it would between two operating system processes. Similarly, static data is not shared between application domains, so static constructors run for each app domain; assuming a single thread per app domain, there is no need to synchronize the static data because each application has its own instance of the data. Furthermore, each application domain has its own threads, and just like with an operating system process, threads cannot cross app domain boundaries.

The point of an application domain is that processes are considered relatively expensive. With application domains, you can avoid this additional expense by running multiple app domains within a single process. For example, you can use a single process to host a series of websites, but then isolate the websites from one another by placing them in their own application domains. In summary, application domains represent a virtual process on a layer between an operating system process and the threads.

Assemblies, Manifests, and Modules

Included in the CLI is the specification of the CIL output from a source language compiler, usually an assembly. In addition to the CIL instructions themselves, an assembly includes a manifest that is made up of the following components:

• The types that an assembly defines and imports

• Version information about the assembly itself

• Additional files the assembly depends on

• Security permissions for the assembly

The manifest is essentially a header to the assembly, providing all the information about what an assembly is composed of, along with the information that uniquely identifies it.

Assemblies can be class libraries or the executables themselves, and one assembly can reference other assemblies (which, in turn, can reference more assemblies), thereby establishing an application composed of many components rather than existing as one large, monolithic program. This is an important feature that modern programming platforms take for granted, because it significantly improves maintainability and allows a single component to be shared across multiple programs.

In addition to the manifest, an assembly contains the CIL code within one or more modules. Generally, the assembly and the manifest are combined into a single file, as was the case with HelloWorld.exe in Chapter 1. However, it is possible to place modules into their own separate files and then use an assembly linker (al.exe) to create an assembly file that includes a manifest that references each module.3 This approach not only provides another means of breaking a program into components, but also enables the development of one assembly using multiple source languages.

3. This is partly because one of the primary CLI IDEs, Visual Studio .NET, lacks functionality for working with assemblies composed of multiple modules. Current implementations of Visual Studio .NET do not have integrated tools for building multimodule assemblies, and when they use such assemblies, IntelliSense does not fully function.

Casually, the terms module and assembly are somewhat interchangeable. However, the term assembly is predominant for those talking about CLI-compatible programs or libraries. Figure 21.2 depicts the various component terms.

Image

FIGURE 21.2: Assemblies with the Modules and Files They Reference

Note that both assemblies and modules can also reference files such as resource files that have been localized to a particular language. Although it is rare, two different assemblies can reference the same module or file.

Even though an assembly can include multiple modules and files, the entire group of files has only one version number, which is placed in the assembly manifest. Therefore, the smallest versionable component within an application is the assembly, even if that assembly is composed of multiple files. If you change any of the referenced files—even to release a patch—without updating the assembly manifest, you will violate the integrity of the manifest and the entire assembly itself. As a result, assemblies form the logical construct of a component or unit of deployment.


Note

Assemblies—not the individual modules that compose them—form the smallest unit that can be versioned and installed.


Even though an assembly (the logical construct) could consist of multiple modules, most assemblies contain only one. Furthermore, Microsoft provides an ILMerge.exe utility for combining multiple modules and their manifests into a single file assembly.

Because the manifest includes a reference to all the files an assembly depends on, it is possible to use the manifest to determine an assembly’s dependencies. Furthermore, at execution time, the runtime needs to examine only the manifest to determine which files it requires. Only tool vendors distributing libraries shared by multiple applications (Microsoft, for example) need to register those files at deployment time. This makes deployment significantly easier. Often, deployment of a CLI-based application is referred to as xcopy deployment, after the Windows xcopycommand that simply copies files to a selected destination.


Language Contrast: COM DLL Registration

Unlike Microsoft’s COM files of the past, CLI assemblies rarely require any type of registration. Instead, it is possible to deploy applications by copying all the files that compose a program into a particular directory, and then executing the program.


Common Intermediate Language

In keeping with the Common Language Infrastructure name, another important feature of the CIL and the CLI is to support the interaction of multiple languages within the same application (instead of portability of source code across multiple operating systems). As a result, the CIL is the intermediate language not only for C#, but also for many other languages, including Visual Basic .NET, the Java-like language of J#, some incantations of Smalltalk, C++, and a host of others (more than 20 at the time of this writing, including versions of COBOL and FORTRAN). Languages that compile to the CIL are termed source languages, and each has a custom compiler that converts the source language to the CIL. Once compiled to the CIL, the source language is insignificant. This powerful feature enables the development of libraries by different development groups across multiple organizations, without concern for the language choice of a particular group. Thus, the CIL enables programming language interoperability as well as platform portability.


Note

A powerful feature of the CLI is its support for multiple languages. This enables the creation of programs using multiple languages and the accessibility of libraries written in one language from code written in a different language.


Common Type System

Regardless of the programming language, the resultant program operates internally on data types; therefore, the CLI includes the Common Type System (CTS). The CTS defines how types are structured and laid out in memory, as well as the concepts and behaviors that surround types. It includes type manipulation directives alongside the information about the data stored within the type. The CTS standard applies to how types appear and behave at the external boundary of a language because the purpose of the CTS is to achieve interoperability between languages. It is the responsibility of the runtime at execution time to enforce the contracts established by the CTS.

Within the CTS, types are classified into two categories.

Values are bit patterns used to represent basic types, such as integers and characters, as well as more complex data in the form of structures. Each value type corresponds to a separate type designation not stored within the bits themselves. The separate type designation refers to the type definition that provides the meaning of each bit within the value and the operations that the value supports.

Objects contain within them the object’s type designation. (This helps in enabling type checking.) Objects have identity that makes each instance unique. Furthermore, objects have slots that can store other types (either values or object references). Unlike with values, changing the contents of a slot does not change the identity of the object.

These two categories of types translate directly to C# syntax that provides a means of declaring each type.

Common Language Specification

Since the language integration advantages provided by the CTS generally outweigh the costs of implementing it, the majority of source languages support the CTS. However, there is also a subset of CTS language conformance called the Common Language Specification (CLS), whose focus is on library implementations. The CLS is intended for library developers, and provides them with standards for writing libraries that are accessible from the majority of source languages, regardless of whether the source languages using the library are CTS-compliant. It is called the Common Language Specification because it is intended to also encourage CLI languages to provide a means of creating interoperable libraries, or libraries that are accessible from other languages.

For example, although it is perfectly reasonable for a language to provide support for an unsigned integer, such a type is not included as part of the CLS. Therefore, developers implementing a class library should not externally expose unsigned integers because doing so would cause the library to be less accessible from CLS-compliant source languages that do not support unsigned integers. Ideally, then, any libraries that are to be accessible from multiple languages should conform to the CLS. Note that the CLS is not concerned with types that are not exposed externally to the assembly.

Also note that it is possible to have the compiler issue a warning when you create an API that is not CLS-compliant. To accomplish this, you use the assembly attribute System.CLSCompliant and specify a value of true for the parameter.

Base Class Library

In addition to providing a platform in which CIL code can execute, the CLI defines a core set of class libraries that programs may employ, called the Base Class Library (BCL). These libraries provide foundational types and APIs, allowing programs to interact with the runtime and underlying operating system in a consistent manner. The BCL includes support for collections, simple file access, some security, fundamental data types (string, among others), streams, and the like.

Similarly, a Microsoft-specific library called the Framework Class Library (FCL) includes support for rich client user interfaces, web user interfaces, database access, distributed communication, and more.

Metadata

In addition to execution instructions, CIL code includes metadata about the types and files included in a program. The metadata includes the following items:

• A description of each type within a program or class library

• The manifest information containing data about the program itself, along with the libraries it depends on

• Custom attributes embedded in the code, providing additional information about the constructs the attributes decorate

The metadata is not a cursory, nonessential add-on to the CIL. Rather, it represents a core component of the CLI implementation. It provides the representation and the behavior information about a type and includes location information about which assembly contains a particular type definition. It serves a key role in saving data from the compiler and making it accessible at execution time to debuggers and the runtime. This data not only is available in the CIL code, but also is accessible during machine code execution so that the runtime can continue to make any necessary type checks.

Metadata provides a mechanism for the runtime to handle a mixture of native and managed code execution. Also, it increases code and execution robustness because it smooths the migration from one library version to the next, replacing compile-time–defined binding with a load-time implementation.

All header information about a library and its dependencies is found in a portion of the metadata known as the manifest. As a result, the manifest portion of the metadata enables developers to determine a module’s dependencies, including information about particular versions of the dependencies and signatures indicating who created the module. At execution time, the runtime uses the manifest to determine which dependent libraries to load, whether the libraries or the main program has been tampered with, and whether assemblies are missing.

The metadata also contains custom attributes that may decorate the code. Attributes provide additional metadata about CIL instructions that are accessible via the program at execution time.

Metadata is available at execution time by a mechanism known as reflection. With reflection, it is possible to look up a type or its member at execution time and then invoke that member or determine whether a construct is decorated with a particular attribute. This provides late binding,in which the system determines which code to execute at execution time rather than at compile time. Reflection can even be used for generating documentation by iterating through metadata and copying it into a help document of some kind (see Chapter 17).

Summary

This chapter described many new terms and acronyms that are important for understanding the context under which C# programs run. The preponderance of three-letter acronyms can be confusing. Table 21.2 provides a summary list of the terms and acronyms that are part of the CLI.

Image

Image

TABLE 21.2: Common C#-Related Acronyms

A. Downloading and Installing the C# Compiler and CLI Platform

To compile and run C# programs, it is necessary to install a version of the compiler and the CLI platform.

Microsoft .NET for Windows

The predominant CLI platform is Microsoft .NET, which is the platform of choice for development on Microsoft Windows.

• For a rich IDE that includes IntelliSense and support for project files, install a version of the Visual Studio IDE. The “Community Edition” of Visual Studio 2015 is free and contains everything you need to make server applications, rich client applications, and cross-platform mobile applications. This edition, along with other editions of Visual Studio, is available at http://visualstudio.com.

• Microsoft provides free, redistributable packages that include the .NET Framework and compiler binaries for all versions of .NET going back to version 2.0. These are available in the downloads section at http://www.microsoft.com/net. At the time of this book’s writing, the latest released version is .NET 4.6.

Visual Studio Compilation

In 2015, Microsoft made Visual Studio 2015 Community1 available for free. This is a full version of the company’s IDE that provides a premium platform for .NET software development. One of the primary advantages of developing with Visual Studio is the ability to create and open entire projects of files and even combine such projects together into solutions.

1. Visual Studio Express was available or free prior to 2015 and is also sufficient for compiling all samples in this book save those depending on C# 6.0 features.

The associated source code2 includes a solution file, EssentialCSharp.sln, that can be opened up with Visual Studio 2015, compiled (built), and run. After opening the solution file, use the Build->Build Solution menu to compile the code.

2. The source code available for this book (along with some chapters related to earlier versions of C#) is available for download at IntelliTect.com/EssentialCSharp. You can also download the code from http://itl.tc/EssentialCSharpSCC.

Before you can execute the source code, you need to select which project to execute by selecting the associated chapter’s project as the startup project. For example, to execute the samples in Chapter 1, you would right-click on the Chapter01 project and choose Set As Startup Project. Failure to choose the correct chapter will result in an exception with the message (“Error, could not run the Listing....”) when you specify the listing number at execution time.

Once you have selected the correct project, you can run the project from the Debug->Start Without Debugging menu. Alternatively, if you wish to debug the project, you can use Debug->Start Debugging. Once running, the program will prompt for the listing (for example, 18.33) that you wish to execute. As mentioned earlier, you can enter only listings from the project that was set to start up.

Many of the listings have corresponding unit tests. To execute a particular test, open the test project and navigate to the test corresponding to the listing you wish to execute. From there, right-click on the test method and choose either Run Tests (Ctrl+R, T) or Debug Tests (Ctrl+R, Ctrl+T).

Setting up the Compiler Path for Command-Line Compilation

For command-line compilation, regardless of whether you are working with Visual Studio or just the runtime, you must set the PATH environment variable to include the C# compiler, CSC.EXE. If Visual Studio is installed on your computer, open the command prompt from the Start menu by selecting the Developer Command Prompt shortcut from the Visual Studio section of the Windows Start menu. This command prompt places CSC.EXE in the path to be available for execution from any directory.

Without Visual Studio installed, no special compiler command prompt item appears in the Start menu. Instead, you need to reference the full compiler pathname explicitly or add it to the path. The compiler is located at %Windir%\Microsoft.NET\Framework\<version>, where<version> is the version of the .NET Framework (typically v4.0.30319, which contains the compiler tools for all versions of .NET since .NET 4.0) and %Windir% is the environment variable that points to the location of the Windows directory. To add this location to the path, use Set PATH=%PATH%;%Windir%\Microsoft.NET\Framework\<version>, again substituting the value of <version> appropriately. Output A.1 provides an example.

OUTPUT A.1

Set PATH=%PATH%;%Windir%\Microsoft.NET\Framework\v4.0.30319

Once the path includes the framework, you can then use the .NET C# compiler, CSC.EXE, without providing the full path to its location.

.NET on OS X and Linux

Since its initial release, there has always been cross platforms versions of the CLI that allow execution of .NET and CIL code on operating systems in addition to Windows. As of this writing, the key platforms are the .NET Core project (CoreCLR) and Mono. Both of these are open source implementations that allow development on OS X and Linux (in addition to Windows).

.NET Core

Microsoft provides an open-source version of the “.NET Core“ version of the .NET Framework for OS X and Linux (in addition to Windows); at the time of this book’s writing, it was in preview release with development driven primarily by ASP.NET 5. Sources and binary downloads are available at https://github.com/dotnet/coreclr.

Unlike the full .NET Framework on Windows, the .NET Core project does not require installing the .NET Framework as part of the operating system. Rather, all the files you need are copied to a single directory. To prepare the environment requires downloading some general-purpose packages so that .NET can be installed. Next, it is necessary to install the .NET Version Manager followed by the .NET Core Execution Environment (DNX). This environment provides the engine for executing CIL under the CoreCLR implementation. Finally, once everything is set up, the environment is ready to compile and run your C# code.

Execution of managed executables created with the .NET Framework is built into the Windows operating system, but not into OS X or Linux (or even on Windows with .NET Core). To execute the generated binary, you use a utility that launches the executable in the CLR; it is activated by executing dnx.exe.

For complete and up-to-date instructions for installation and execution using .NET Core, see http://itl.tc/GettingStartedWithDNX.

Note that the Core .NET project on OS X and Linux is intended for writing high-performance server-side code, not for writing graphical user interface applications; there is no GUI library package included with Core .NET. To make applications with user interfaces on non-Windows platforms, use Mono.

Mono

For CLI development on platforms other than Microsoft Windows, consider Mono, which is a platform you can download at http://www.mono-project.com. As with the .NET platform, Mono requires the full path to the C# compiler if it is not already in the search path. The default installation path on Linux is /usr/lib/mono/<version>, and the compiler is gmcs.exe or mcs.exe, depending on the version. (If Mono is installed on Microsoft Windows, the default path is %ProgramFiles%\Mono-<version>\lib\mono\<version>\.)

One option for a Linux version that includes an installation of Mono is Monoppix. This version builds on the CD-bootable Linux distribution known as Knoppix and is available for download at http://www.monoppix.com.

Instead of CSC.EXE, the Mono platform’s compiler is MCS.EXE or GMCS.EXE, depending on the compiler version. Therefore, the command for compiling HelloWorld.cs is as shown in Output A.2.

OUTPUT A.2

C:\SAMPLES>msc.exe HelloWorld.cs

Unfortunately, the Linux environment cannot run the resultant binaries directly. Instead, it requires explicit execution of the runtime using mono.exe, as shown in Output A.3.

OUTPUT A.3

C:\SAMPLES>mono.exe HelloWorld.exe
Hello. My name is Inigo Montoya.

B. Tic-Tac-Toe Source Code Listing

LISTING B.1: Tic-Tac-Toe


#define CSHARP2

using System;

#pragma warning disable 1030 // Disable user-defined warnings

// The TicTacToe class enables two players to
// play tic-tac-toe.
class TicTacToeGame // Declares the TicTacToeGame class.
{
static void Main() // Declares the entry point to the program.
{

// Stores locations each player has moved.
int[ ] playerPositions = { 0, 0 };

// Initially set the currentPlayer to Player 1.
int currentPlayer = 1;

// Winning player.
int winner = 0;

string input = null;


// Display the board and prompt the current player
// for his next move.
for (int turn = 1; turn <= 10; ++turn)
{

DisplayBoard(playerPositions);

#region Check for End Game
if (EndGame(winner, turn, input))
{
break;
}
#endregion Check for End Game

input = NextMove(playerPositions, currentPlayer);

winner = DetermineWinner(playerPositions);

// Switch players.
currentPlayer = (currentPlayer == 2) ? 1 : 2;
}
}

private static string NextMove(int[] playerPositions,
int currentPlayer)
{

string input;

// Repeatedly prompt the player for a move
// until a valid move is entered.
bool validMove;
do
{

// Request a move from the current player.
System.Console.Write($"\nPlayer {currentPlayer} - Enter move:");
input = System.Console.ReadLine();
validMove = ValidateAndMove(playerPositions,
currentPlayer, input);
} while (!validMove);

return input;
}

static bool EndGame(int winner, int turn, string input)
{
bool endGame = false;
if (winner > 0)
{

System.Console.WriteLine($"\nPlayer {winner} has won!!!!");
endGame = true;
}
else if (turn == 10)
{
// After completing the 10th display of the
// board, exit rather than prompting the
// user again.
System.Console.WriteLine("\nThe game was a tie!");
endGame = true;
}
else if (input == "" || input == "quit")
{
// Check if user quit by hitting Enter without
// any characters or by typing "quit".
System.Console.WriteLine("The last player quit");
endGame = true;
}
return endGame;
}

static int DetermineWinner(int[] playerPositions)
{
int winner = 0;

// Determine if there is a winner.
int[] winningMasks = {
7, 56, 448, 73, 146, 292, 84, 273};

foreach (int mask in winningMasks)
{
if ((mask & playerPositions[0]) == mask)
{
winner = 1;
break;
}
else if ((mask & playerPositions[1]) == mask)
{
winner = 2;
break;
}
}
return winner;
}

static bool ValidateAndMove(
int[] playerPositions, int currentPlayer, string input)
{
bool valid = false;

// Check the current player's input.
switch (input)
{
case "1":
case "2":
case "3":
case "4":
case "5":
case "6":
case "7":
case "8":
case "9":
#warning "Same move allowed multiple times."
int shifter; // The number of places to shift
// over to set a bit.

int position; // The bit which is to be set.

// int.Parse() converts "input" to an integer.
// "int.Parse(input) – 1" because arrays
// are zero-based.
shifter = int.Parse(input) - 1;

// Shift mask of 00000000000000000000000000000001
// over by cellLocations.
position = 1 << shifter;

// Take the current player cells and OR them
// to set the new position as well.
// Since currentPlayer is either 1 or 2, you
// subtract 1 to use currentPlayer as an
// index in a zero-based array.
playerPositions[currentPlayer - 1] |= position;

valid = true;
break;

case "":
case "quit":
valid = true;
break;

default:
// If none of the other case statements
// is encountered, then the text is invalid.
System.Console.WriteLine(
"\nERROR: Enter a value from 1-9. "
+ "Push ENTER to quit");
break;
}

return valid;
}

static void DisplayBoard(int[] playerPositions)
{
// This represents the borders between each cell
// for one row.
string[] borders = {
"|", "|", "\n---+---+---\n", "|", "|",
"\n---+---+---\n", "|", "|", ""
};

// Display the current board;
int border = 0; // set the first border (border[0] = "|").

#if CSHARP2
System.Console.Clear();
#endif

for (int position = 1;
position <= 256;
position <<= 1, border++)
{
char token = CalculateToken(
playerPositions, position);

// Write out a cell value and the border that
// comes after it.
System.Console.Write($" {token} {borders[border]}");
}
}

static char CalculateToken(
int[] playerPositions, int position)
{
// Initialize the players to 'X' and 'O'
char[] players = {'X', 'O'};

char token;
// If player has the position set,
// then set the token to that player.
if ((position & playerPositions[0]) == position)
{
// Player 1 has that position marked.
token = players[0];
}
else if ((position & playerPositions[1]) == position)
{
// Player 2 has that position marked.
token = players[1];
}
else
{
// The position is empty.
token = ' ';
}
return token;
}

#line 113 "TicTacToe.cs"
// Generated code goes here.
#line default