The .NET Runtime Environment with C#

In the past, writing modules that could be called from multiple languages was difficult. Code that’s written in Visual Basic can’t be called from Visual C++. Code that’s written in Visual C++ can sometimes be called from Visual Basic, but it’s not easy to do. Visual C++ uses the C and C++ runtimes, which have specific behavior, and Visual Basic uses its own execution engine, also with its own specific—and different—behavior.

And so the Component Object Model (COM) was created, and it has been pretty successful as a way of writing component-based software. Unfortunately, it’s fairly difficult to use from the Visual C++ world, and it’s not fully featured in the Visual Basic world. And therefore, it was used extensively when writing COM components but was used less often when writing native applications. So, if one programmer wrote some nice code in C++ and another wrote some in Visual Basic, there really wasn’t an easy way to work together.

Further, the world was tough for library providers, as no one choice would work in all markets. If the writer thought the library was targeted toward the Visual Basic crowd, it’d be easy to use from Visual Basic, but that choice might either constrain access from the C++ perspec­tive or come with an unacceptable performance penalty. Or, a library could be written for C++ users for good performance and low-level access, but it’d ignore the Visual Basic programmers.

Sometimes a library would be written for both types of users, but this usually meant some compromises had to happen. To send e-mail on a Windows system, for example, you have a choice between Collaboration Data Objects (CDO), which is a COM-based interface that can be called from both languages but doesn’t do everything,1 and native Messaging Application Programming Interface (MAPI) functions (in both C and C++ versions) that can access all functions.

The .NET runtime is designed to remedy this situation. It has one way of describing code (metadata) and one runtime and library (the CLR and .NET Framework). Figure 2-1 shows how the .NET runtime is arranged.

The CLR provides the basic execution services. On top of that, the base classes provide basic data types, collection classes, and other general classes. Built on top of the base classes are classes for dealing with data and Extensible Markup Language (XML). Finally, at the top of the architecture are classes to expose Web services and to deal with the user interface. An application may call in at any level and use classes from any level.

To understand how C# works, it’s important to understand a bit about the .NET runtime and the .NET Framework. The following section provides an overview; you can find more detailed information in Chapter 38.

1. The Execution Environment

This section was once titled “The Execution Engine,” but the .NET runtime is much more than just an engine. The environment provides a simpler programming model, safety and security, powerful tools support, and help with deployment, packaging, and other support.

2.1. A Simpler Programming Model

All services are offered through a common model that can be accessed equally through all the .NET languages, and the services can be written in any .NET language. The environment is largely language-agnostic, allowing language choice. This makes code reuse easier, both for the programmer and for the library providers.

The environment also supports using existing code in C# code, either through calling functions in Dynamic Link Libraries (DLLs) or making COM components appear to be .NET runtime components. .NET runtime components can also be used in situations that require COM components.

In contrast with the various error-handling techniques in existing libraries, in the .NET runtime all errors are reported via exceptions. You have no need to switch between error codes, HRESULTs, and exceptions.

Finally, the environment contains the .NET Framework, which provides the functions tradi­tionally found in runtime libraries, plus a few new ones. The framework is divided into different categories.

System

The System namespace contains the core classes for the runtime. These classes are roughly analogous to the C++ runtime library and include the nested namespaces described in Table 2-1.

System.Data

The System.Data namespace contains the classes that support database operations (see Table 2-2).

System.Xml

The System.Xml namespace contains classes to manage XML.

System.Drawing

The System.Drawing namespace contains classes that support GDI+, including printing and imaging.

System.Web

The System.Web namespace contains classes for dealing with Web services and classes for creating Web-based interfaces using ASP.NET.

System.Windows.Forms

The System.Windows.Forms namespace contains classes to create rich-client interfaces.

2.2. Safety and Security

The .NET runtime environment is designed to be a safe and secure environment. The .NET runtime is a managed environment, which means that the runtime manages memory for the programmer. Instead of having to manage memory allocation and deallocation, the garbage collector does it. Not only does garbage collection reduce the number of things to remember when programming, in a server environment it can drastically reduce the number of memory leaks. This makes high-availability systems much easier to develop.

Additionally, the .NET runtime is a verified environment. At runtime, the environment verifies that the executing code is type-safe. This can catch errors, such as passing the wrong type to a function, and can catch attacks, such as trying to read beyond allocated boundaries or executing code at an arbitrary location.

The security system interacts with the verifier to ensure that code does only what it’s permitted to do. The security requirements for a specific piece of code can be expressed in a finely grained manner; code can, for example, specify that it needs to be able to write a scratch file, and that requirement will be checked during execution.

2.3 Powerful Tools Support

Microsoft supplies four .NET languages: Visual Basic, C#, C++/CLI and J#. Other companies are working on compilers for other languages that run the gamut from COBOL to Perl.

Debugging is greatly enhanced in the .NET runtime. The common execution model makes cross-language debugging simple and straightforward, and debugging can seamlessly span code written in different languages and running in different processes or on different machines.

Finally, all .NET programming tasks are tied together by the Visual Studio environment, which gives support for designing, developing, debugging, and deploying applications.

2.4. Deployment, Packaging, and Support

The .NET runtime helps out in these areas as well. Deployment has been simplified, and in some cases there isn’t a traditional install step. Because the packages are deployed in a general format, a single package can run in any environment that supports .NET. Finally, the environ­ment separates application components so that an application runs only with the components it shipped with, rather than with different versions shipped by other applications.

.NET 2.0 again simplifies the deployment process with a new technology called ClickOnce, which allows a Windows Forms application to be deployed in a manner that’s conceptually similar to the deployment model of a Web application. Chapter 37 walks you through deploying an application with ClickOnce.

3. Metadata

Metadata is the glue that holds the .NET runtime together. Metadata is the analog of the type library in the COM world but with much more extensive information.

For every object that’s part of the .NET world, the metadata for that object records all the information that’s required to use the object, which includes the following:

  • The name of the object
  • The names of all the fields of the object and their types
  • The names of all member functions, including parameter types and names

With this information, the .NET runtime is able to figure out how to create objects, call member functions, or access object data, and compilers can use them to find out what objects are available and how an object is used.

This unification is nice for both the producer and the consumer of code; the producer of code can easily author code that can be used from all .NET-compatible languages, and the user of the code can easily use objects created by others, regardless of the language that the objects are implemented in.

Additionally, this rich metadata allows other tools access to detailed information about the code. The Visual Studio shell uses this information in the Object Browser and for features such as IntelliSense.

Finally, runtime code can query the metadata—in a process called reflection—to find out what objects are available and what functions and fields are present on the class. This is similar to dealing with IDispatch in the COM world but with a simpler model. Of course, such access isn’t strongly typed, so most software will choose to reference the metadata at compile time rather than runtime, but it’s a useful facility for applications such as scripting languages.

Finally, reflection is available to the end user to determine what objects look like, to search for attributes, or to execute methods whose names aren’t known until runtime.

4. Assemblies

In the past, a finished software package might have been released as an executable, as DLL and LIB files, as a DLL containing a COM object and a typelib, or as some other mechanism.

In the .NET runtime, the mechanism of packaging is the assembly. When code is compiled by one of the .NET compilers, it’s converted to an intermediate form known as Intermediate Language (IL). The assembly contains all the IL, metadata, and other files required for a package to run—in one complete package. Each assembly contains a manifest that enumerates the files contained in the assembly, controls what types and resources are exposed outside the assembly, and maps references from those types and resources to the files that contain the types and resources. The manifest also lists the other assemblies that an assembly depends upon.

Assemblies are self-contained; enough information exists in the assembly for it to be self-describing.

When defining an assembly, the assembly can be contained in a single file, or it can be split amongst several files. Using several files will enable a scenario where sections of the assembly are downloaded only as needed.

5. Language Interop

One of the goals of the .NET runtime is to be language-agnostic, allowing code to be used and written from whatever language is convenient. Not only can classes written in Visual Basic be called from C# or C++ (or any other .NET language), a class that was written in Visual Basic can be used as a base class for a class written in C#, and that class could be used from a C++ class.

In other words, it shouldn’t matter which language a class was authored in. In fact, it often isn’t possible to tell what language a class was written in.

In practice, this goal runs into a few obstacles. Some languages have unsigned types that aren’t supported by other languages, and some languages support operator overloading. Allowing the more feature-rich languages to retain their freedom of expression while still making sure their classes can interop with other languages is challenging.

To support this, the .NET runtime has sufficient support to allow the feature-rich languages full expressibility, so code that’s written in one of those languages isn’t constrained by the simpler languages.

For classes to be usable from .NET languages in general, the classes must adhere to the Common Language Specification (CLS), which describes what features can be visible in the public interface of the class (any features can be used internally in a class). For example, the CLS prohibits exposing unsigned data types because not all languages can use them. You can find more information on the CLS in the “Cross-Language Interoperability” section of the .NET software development kit (SDK).

A user writing C# code can indicate that it’s supposed to be CLS compliant, and the compiler will flag any noncompliant areas. For more information on the specific restrictions placed on C# code by CLS compliance, see Chapter 38.

6. Attributes

To transform a class into a component, you’ll often need some additional information, such as how to persist a class to disk or how transactions should be handled. The traditional approach is to write the information in a separate file and then combine it with the source code to create a component.

The problem with this approach is that information is duplicated in multiple places. It’s cumbersome and error-prone, and it means you don’t have the whole component unless you have both files.

The .NET runtime supports custom attributes (known simply as attributes in C#), which are a way to place descriptive information in the metadata along with an object and then retrieve the data later. Attributes provide a general mechanism for doing this, and they’re used heavily throughout the runtime to store information that modifies how the runtime uses the class.

Attributes are fully extensible, and this allows programmers to define attributes and use them.

Source: Gunnerson Eric, Wienholt Nick (2005), A Programmer’s Introduction to C# 2.0, Apress; 3rd edition.

Leave a Reply

Your email address will not be published. Required fields are marked *