Home
C# Language C# Language
C# Language Contents C# Language C# Language C# Language

C# with .NET

Prev Page Next PageNext C# Language
C# Language   C# Language
C# Language Table of Contents
C# Language Back Cover
C# Language Professional C# 2009 with .NET 3.0
C# Language Introduction
C# Language Looking at What’s New in the .NET Framework 2.0
C# Language Introducing the .NET Framework 3.0
C# Language Where C# Fits In
C# Language What You Need to Write and Run C# Code
C# Language What This site Covers
C# Language Conventions
C# Language Source Code
C# Language Errata
C# Language roque-patrick.com
C# Language The C# Language
C# Language .NET Architecture
C# Language The Relationship of C# to .NET
C# Language The Common Language Runtime
C# Language A Closer Look at Intermediate Language
C# Language Assemblies
C# Language .NET Framework Classes
C# Language Namespaces
C# Language Creating .NET Applications Using C#
C# Language The Role of C# in the .NET Enterprise Architecture
C# Language Summary
C# Language C# Basics
C# Language Before We Start
C# Language Your First C# Program
C# Language Variables
C# Language Predefined Data Types
C# Language Flow Control
C# Language Enumerations
C# Language Arrays
C# Language Namespaces
C# Language The Main() Method
C# Language More on Compiling C# Files
C# Language Console I/O
C# Language Using Comments
C# Language The C# Preprocessor Directives
C# Language C# Programming Guidelines
C# Language Summary
C# Language Objects and Types
C# Language Classes and Structs
C# Language Class Members
C# Language Structs
C# Language Partial Classes
C# Language Static Classes
C# Language The Object Class
C# Language Summary
C# Language Inheritance
C# Language Implementation Inheritance
C# Language Modifiers
C# Language Interfaces
C# Language Summary
C# Language Arrays
C# Language Simple Arrays
C# Language Multidimensional Arrays
C# Language Jagged Arrays
C# Language Array Class
C# Language Array and Collection Interfaces
C# Language Enumerations
C# Language Summary
C# Language Operators and Casts
C# Language Operators
C# Language Type Safety
C# Language Comparing Objects for Equality
C# Language Operator Overloading
C# Language User-Defined Casts
C# Language Summary
C# Language Delegates and Events
C# Language Delegate Inference
C# Language Anonymous Methods
C# Language Events
C# Language Summary
C# Language Strings and Regular Expressions
C# Language System.String
C# Language Regular Expressions
C# Language Summary
C# Language Generics
C# Language Overview
C# Language Creating Generic Classes
C# Language Generic Classes’ Features
C# Language Generic Interfaces
C# Language Generic Methods
C# Language Generic Delegates
C# Language Other Generic Framework Types
C# Language Summary
C# Language Collections
C# Language Collection Interfaces and Types
C# Language Lists
C# Language Queue
C# Language Stack
C# Language Linked Lists
C# Language Sorted Lists
C# Language Dictionaries
C# Language Dictionary with Multiple Keys
C# Language Bit Arrays
C# Language Performance
C# Language Summary
C# Language Memory Management and Pointers
C# Language Memory Management under the Hood
C# Language Freeing Unmanaged Resources
C# Language Unsafe Code
C# Language Summary
C# Language Reflection
C# Language Custom Attributes
C# Language Reflection
C# Language Summary
C# Language Errors and Exceptions
C# Language Looking into Errors and Exception Handling
C# Language Summary
C# Language Visual Studio
C# Language Visual Studio 2009
C# Language Refactoring
C# Language Visual Studio 2009 for .NET Framework 3.0
C# Language Summary
C# Language Deployment
C# Language Designing for Deployment
C# Language Deployment Options
C# Language Deployment Requirements
C# Language Deploying the .NET Runtime
C# Language Simple Deployment
C# Language Installer Projects
C# Language ClickOnce
C# Language Summary
C# Language Base Class Libraries
C# Language Assemblies
C# Language What Are Assemblies?
C# Language Assembly Structure
C# Language Cross-Language Support
C# Language Global Assembly Cache
C# Language Creating Shared Assemblies
C# Language Configuration
C# Language Summary
C# Language Tracing and Events
C# Language Tracing
C# Language Event Logging
C# Language Performance Monitoring
C# Language Summary
C# Language Threading and Synchronization
C# Language Overview
C# Language Asynchronous Delegates
C# Language The Thread Class
C# Language Thread Pools
C# Language Threading Issues
C# Language Synchronization
C# Language COM Apartments
C# Language Background Worker
C# Language Summary
C# Language .NET Security
C# Language Code Access Security
C# Language Support for Security in the Framework
C# Language Managing Security Policies
C# Language Role-Based Security
C# Language Summary
C# Language Localization
C# Language Namespace System.Globalization
C# Language Resources
C# Language Localization Example Using Visual Studio
C# Language Localization with ASP.NET
C# Language A Custom Resource Reader
C# Language Creating Custom Cultures
C# Language Summary
C# Language Transactions
C# Language Overview
C# Language Database and Classes
C# Language Traditional Transactions
C# Language System.Transactions
C# Language Isolation Level
C# Language Custom Resource Managers
C# Language Transactions with Windows Vista
C# Language Summary
C# Language Windows Services
C# Language What Is a Windows Service?
C# Language Windows Services Architecture
C# Language System.ServiceProcess Namespace
C# Language Creating a Windows Service
C# Language Monitoring and Controlling the Service
C# Language Troubleshooting
C# Language Power Events
C# Language Summary
C# Language COM Interoperability
C# Language .NET and COM
C# Language Marshaling
C# Language Using a COM Component from a .NET Client
C# Language Using a .NET Component from a COM Client
C# Language Platform Invoke
C# Language Summary
C# Language Data
C# Language Manipulating Files and the Registry
C# Language Managing the File System
C# Language Moving, Copying, and Deleting Files
C# Language Reading and Writing to Files
C# Language Reading Drive Information
C# Language File Security
C# Language Reading and Writing to the Registry
C# Language Reading and Writing to Isolated Storage
C# Language Summary
C# Language Data Access with .NET
C# Language ADO.NET Overview
C# Language Using Database Connections
C# Language Commands
C# Language Fast Data Access: The Data Reader
C# Language Managing Data and Relationships: The DataSet Class
C# Language Populating a DataSet
C# Language Persisting DataSet Changes
C# Language Working with ADO.NET
C# Language Summary
C# Language Manipulating XML
C# Language XML Standards Support in .NET
C# Language Introducing the System.Xml Namespace
C# Language Using MSXML in .NET
C# Language Using System.Xml Classes
C# Language Reading and Writing Streamed XML
C# Language Using the DOM in .NET
C# Language Using XPathNavigators
C# Language XML and ADO.NET
C# Language Serializing Objects in XML
C# Language Summary
C# Language .NET Programming with SQL Server 2009
C# Language .NET Runtime Host
C# Language Microsoft.SqlServer.Server
C# Language User-Defined Types
C# Language Stored Procedures
C# Language User-Defined Functions
C# Language Triggers
C# Language XML Data Type
C# Language Summary
C# Language Presentation
C# Language Windows Forms
C# Language Creating a Windows Form Application
C# Language Control Class
C# Language Standard Controls and Components
C# Language Forms
C# Language Summary
C# Language Viewing .NET Data
C# Language The DataGridView Control
C# Language DataGridView Class Hierarchy
C# Language Data Binding
C# Language Visual Studio .NET and Data Access
C# Language Summary
C# Language Graphics with GDI+
C# Language Understanding Drawing Principles
C# Language Measuring Coordinates and Areas
C# Language A Note about Debugging
C# Language Drawing Scrollable Windows
C# Language World, Page, and Device Coordinates
C# Language Colors
C# Language The Safety Palette
C# Language Pens and Brushes
C# Language Drawing Shapes and Lines
C# Language Displaying Images
C# Language Issues When Manipulating Images
C# Language Drawing Text
C# Language Simple Text Example
C# Language Fonts and Font Families
C# Language Example: Enumerating Font Families
C# Language Editing a Text Document: The CapsEditor Sample
C# Language Printing
C# Language Summary
C# Language Windows Presentation Foundation
C# Language Overview
C# Language Shapes
C# Language Controls
C# Language Layout
C# Language Event Handling
C# Language Commands
C# Language Styles, Templates, and Resources
C# Language Styles
C# Language Animations
C# Language Data Binding
C# Language Windows Forms Integration
C# Language Summary
C# Language ASP.NET Pages
C# Language ASP.NET Introduction
C# Language ASP.NET Web Forms
C# Language ADO.NET and Data Binding
C# Language Application Configuration
C# Language Summary
C# Language ASP.NET Development
C# Language Custom Controls
C# Language Master Pages
C# Language Site Navigation
C# Language Security
C# Language Themes
C# Language Web Parts
C# Language Summary
C# Language ASP.NET AJAX
C# Language What Is Ajax?
C# Language What Is ASP.NET AJAX?
C# Language ASP.NET AJAX-Enabled Web Sites
C# Language Summary
C# Language Communication
C# Language Accessing the Internet
C# Language The WebClient Class
C# Language WebRequest and WebResponse Classes
C# Language Displaying Output as an HTML Page
C# Language Utility Classes
C# Language Lower-Level Protocols
C# Language Summary
C# Language Web Services with ASP.NET
C# Language SOAP
C# Language WSDL
C# Language Web Services
C# Language Extending the Event-siteing Example
C# Language Exchanging Data Using SOAP Headers
C# Language Summary
C# Language .NET Remoting
C# Language What Is .NET Remoting?
C# Language .NET Remoting Overview
C# Language Contexts
C# Language Remote Objects, Clients, and Servers
C# Language .NET Remoting Architecture
C# Language Miscellaneous .NET Remoting Features
C# Language Summary
C# Language Enterprise Services
C# Language Overview
C# Language Creating a Simple COM+ Application
C# Language Deployment
C# Language Component Services Explorer
C# Language Client Application
C# Language Transactions
C# Language Sample Application
C# Language Integrating WCF and Enterprise Services
C# Language Summary
C# Language Message Queuing
C# Language Overview
C# Language Message Queuing Products
C# Language Message Queuing Architecture
C# Language Message Queuing Administrative Tools
C# Language Programming Message Queuing
C# Language Course Order Application
C# Language Receiving Results
C# Language Transactional Queues
C# Language Message Queue Installation
C# Language Summary
C# Language Windows Communication Foundation
C# Language Overview
C# Language Simple Service and Client
C# Language Contracts
C# Language Service Implementation
C# Language Binding
C# Language Hosting
C# Language Clients
C# Language Duplex Communication
C# Language Summary
C# Language Windows Workflow Foundation
C# Language Activities
C# Language Custom Activities
C# Language Workflows
C# Language The Workflow Runtime
C# Language Workflow Services
C# Language Hosting Workflows
C# Language The Workflow Designer
C# Language Summary
C# Language Download Details
C# Language Directory Services
C# Language The Architecture of Active Directory
C# Language Administration Tools for Active Directory
C# Language Programming Active Directory
C# Language Searching for User Objects
C# Language DSML
C# Language Summary
C# Language Part VII: Additional Information
C# Language C#, Visual Basic, and C++/CLI
C# Language Namespaces
C# Language Defining Types
C# Language Methods
C# Language Static Members
C# Language Arrays
C# Language Control Statements
C# Language Loops
C# Language Exception Handling
C# Language Inheritance
C# Language Resource Management
C# Language Delegates
C# Language Events
C# Language Generics
C# Language C++/CLI Mixing Native and Managed Code
C# Language Summary
C# Language Windows Vista
C# Language Vista Bridge
C# Language User Account Control
C# Language Directory Structure
C# Language New Controls and Dialogs
C# Language Search
C# Language Summary
C# Language Language Integrated Query
C# Language Traditional Queries
C# Language LINQ Query
C# Language Query Expressions
C# Language Extension Methods
C# Language Standard Query Operators
C# Language Lambda Expressions
C# Language Deferred Query Execution
C# Language Expression Trees
C# Language Type Inference
C# Language Object and Collection Initializers
C# Language Anonymous Types
C# Language Summary
C# Language Index
C# Language A
C# Language B
C# Language C
C# Language D
C# Language E
C# Language F
C# Language G
C# Language H
C# Language I
C# Language J
C# Language K
C# Language L
C# Language M
C# Language N
C# Language O
C# Language P
C# Language Q
C# Language R
C# Language S
C# Language T
C# Language U
C# Language V
C# Language W
C# Language X
C# Language Y
C# Language Z
contractor first aid kit
C# Language
C# Language
Previous PagePrevious
Next PageNext

A Closer Look at Intermediate Language

From what you learned in the previous section, Microsoft Intermediate Language obviously plays a fundamental role in the .NET Framework. As C# developers, we now understand that our C# code will be compiled into IL before it is executed (indeed, the C# compiler only compiles to managed code). It makes sense, then, to now take a closer look at the main characteristics of IL, because any language that targets .NET will logically need to support the main characteristics of IL, too.

Here are the important features of IL:

  • Object orientation and use of interfaces

  • Strong distinction between value and reference types

  • Strong data typing

  • Error handling through the use of exceptions

  • Use of attributes

The following sections take a closer look at each of these characteristics.

Support for Object Orientation and Interfaces

The language independence of .NET does have some practical limitations. IL is inevitably going to implement some particular programming methodology, which means that languages targeting it are going to have to be compatible with that methodology. The particular route that Microsoft has chosen to follow for IL is that of classic object-oriented programming, with single implementation inheritance of classes.

Tip 

If you are unfamiliar with the concepts of object orientation, refer to Appendix A for more information. Appendix A is posted at www.wrox.com.

In addition to classic object-oriented programming, IL also brings in the idea of interfaces, which saw their first implementation under Windows with COM. .NET interfaces are not the same as COM interfaces; they do not need to support any of the COM infrastructure (for example, they are not derived from IUnknown, and they do not have associated globally unique identifiers, more commonly know as GUIDs). However, they do share with COM interfaces the idea that they provide a contract, and classes that implement a given interface must provide implementations of the methods and properties specified by that interface.

You have now seen that working with .NET means compiling to IL, and that in turn means that you will need to use traditional object-oriented methodologies. However, that alone is not sufficient to give you language interoperability. After all, C++ and Java both use the same object-oriented paradigms, but they are still not regarded as interoperable. We need to look a little more closely at the concept of language interoperability.

To start with, we need to consider exactly what we mean by language interoperability. After all, COM allowed components written in different languages to work together in the sense of calling each other’s methods. What was inadequate about that? COM, by virtue of being a binary standard, did allow components to instantiate other components and call methods or properties against them, without worrying about the language the respective components were written in. In order to achieve this, however, each object had to be instantiated through the COM runtime, and accessed through an interface. Depending on the threading models of the relative components, there may have been large performance losses associated with marshaling data between apartments or running components or both on different threads. In the extreme case of components hosted as an executable rather than DLL files, separate processes would need to be created in order to run them. The emphasis was very much that components could talk to each other but only via the COM runtime. In no way with COM did components written in different languages directly communicate with each other, or instantiate instances of each other - it was always done with COM as an intermediary. Not only that, but the COM architecture did not permit implementation inheritance, which meant that it lost many of the advantages of object-oriented programming.

An associated problem was that, when debugging, you would still have to debug components written in different languages independently. It was not possible to step between languages in the debugger. So what we really mean by language interoperability is that classes written in one language should be able to talk directly to classes written in another language. In particular:

  • A class written in one language can inherit from a class written in another language.

  • The class can contain an instance of another class, no matter what the languages of the two classes are.

  • An object can directly call methods against another object written in another language.

  • Objects (or references to objects) can be passed around between methods.

  • When calling methods between languages you can step between the method calls in the debugger, even when this means stepping between source code written in different languages.

This is all quite an ambitious aim, but amazingly, .NET and IL have achieved it. In the case of stepping between methods in the debugger, this facility is really offered by the Visual Studio .NET integrated development environment (IDE) rather than by the CLR itself.

Distinct Value and Reference Types

As with any programming language, IL provides a number of predefined primitive data types. One characteristic of IL, however, is that it makes a strong distinction between value and reference types. Value types are those for which a variable directly stores its data, whereas reference types are those for which a variable simply stores the address at which the corresponding data can be found.

In C++ terms, using reference types can be considered to be similar to accessing a variable through a pointer, whereas for Visual Basic, the best analogy for reference types are objects, which in Visual Basic 6 are always accessed through references. IL also lays down specifications about data storage: instances of reference types are always stored in an area of memory known as the managed heap, whereas value types are normally stored on the stack (although if value types are declared as fields within reference types, they will be stored inline on the heap). Chapter 2, “C# Basics,” discusses the stack and the heap and how they work.

Strong Data Typing

One very important aspect of IL is that it is based on exceptionally strong data typing. That means that all variables are clearly marked as being of a particular, specific data type (there is no room in IL, for example, for the Variant data type recognized by Visual Basic and scripting languages). In particular, IL does not normally permit any operations that result in ambiguous data types.

For instance, Visual Basic 6 developers are used to being able to pass variables around without worrying too much about their types, because Visual Basic 6 automatically performs type conversion. C++ developers are used to routinely casting pointers between different types. Being able to perform this kind of operation can be great for performance, but it breaks type safety. Hence, it is permitted only under certain circumstances in some of the languages that compile to managed code. Indeed, pointers (as opposed to references) are permitted only in marked blocks of code in C#, and not at all in Visual Basic (although they are allowed in managed C++). Using pointers in your code causes it to fail the memory type safety checks performed by the CLR.

You should note that some languages compatible with .NET, such as Visual Basic 2009, still allow some laxity in typing, but that is only possible because the compilers behind the scenes ensure that the type safety is enforced in the emitted IL.

Although enforcing type safety might initially appear to hurt performance, in many cases the benefits gained from the services provided by .NET that rely on type safety far outweigh this performance loss. Such services include:

  • Language interoperability

  • Garbage collection

  • Security

  • Application domains

The following sections take a closer look at why strong data typing is particularly important for these features of .NET.

The Importance of Strong Data Typing for Language Interoperability

If a class is to derive from or contains instances of other classes, it needs to know about all the data types used by the other classes. This is why strong data typing is so important. Indeed, it is the absence of any agreed-on system for specifying this information in the past that has always been the real barrier to inheritance and interoperability across languages. This kind of information is simply not present in a standard executable file or DLL.

Suppose that one of the methods of a Visual Basic 2009 class is defined to return an Integer - one of the standard data types available in Visual Basic 2009. C# simply does not have any data type of that name. Clearly, you will only be able to derive from the class, use this method, and use the return type from C# code, if the compiler knows how to map Visual Basic 2009’s Integer type to some known type that is defined in C#. So, how is this problem circumvented in .NET?

Common Type System

This data type problem is solved in .NET through the use of the Common Type System (CTS). The CTS defines the predefined data types that are available in IL, so that all languages that target the .NET Framework will produce compiled code that is ultimately based on these types.

For the previous example, Visual Basic 2009’s Integer is actually a 32-bit signed integer, which maps exactly to the IL type known as Int32. This will therefore be the data type specified in the IL code. Because the C# compiler is aware of this type, there is no problem. At source code level, C# refers to Int32 with the keyword int, so the compiler will simply treat the Visual Basic 2009 method as if it returned an int.

The CTS doesn’t specify merely primitive data types but a rich hierarchy of types, which includes well-defined points in the hierarchy at which code is permitted to define its own types. The hierarchical structure of the CTS reflects the single-inheritance object-oriented methodology of IL, and resembles Figure 1-1.

Image from site
Figure 1-1

The following table explains the types shown in Figure 1-1.

Open table as spreadsheet

Type

Meaning

Type

Base class that represents any type.

Value Type

Base class that represents any value type.

Reference Types

Any data types that are accessed through a reference and stored on the heap.

Built-in Value Types

Includes most of the standard primitive types, which represent numbers, Boolean values, or characters.

Enumerations

Sets of enumerated values.

User-defined Value Types

Types that have been defined in source code and are stored as value types. In C# terms, this means any struct.

Interface Types

Interfaces.

Pointer Types

Pointers.

Self-describing Types

Data types that provide information about themselves for the benefit of the garbage collector (see the next section).

Arrays

Any type that contains an array of objects.

Class Types

Types that are self-describing but are not arrays.

Delegates

Types that are designed to hold references to methods.

User-defined Reference Types

Types that have been defined in source code and are stored as reference types. In C# terms, this means any class.

Boxed Value Types

A value type that is temporarily wrapped in a reference so that it can be stored on the heap.

We won’t list all of the built-in value types here, because they are covered in detail in Chapter 3, “Objects and Types.” In C#, each predefined type recognized by the compiler maps onto one of the IL built-in types. The same is true in Visual Basic 2009.

Common Language Specification

The Common Language Specification (CLS) works with the CTS to ensure language interoperability. The CLS is a set of minimum standards that all compilers targeting .NET must support. Because IL is a very rich language, writers of most compilers will prefer to restrict the capabilities of a given compiler to only support a subset of the facilities offered by IL and the CTS. That is fine, as long as the compiler supports everything that is defined in the CLS.

Important 

It is perfectly acceptable to write non-CLS-compliant code. However, if you do, the compiled IL code isn’t guaranteed to be fully language interoperable.

For example, take case sensitivity. IL is case sensitive. Developers who work with case-sensitive languages regularly take advantage of the flexibility that this case sensitivity gives them when selecting variable names. Visual Basic 2009, however, is not case sensitive. The CLS works around this by indicating that CLS-compliant code should not expose any two names that differ only in their case. Therefore, Visual Basic 2009 code can work with CLS-compliant code.

This example shows that the CLS works in two ways. First, it means that individual compilers do not have to be powerful enough to support the full features of .NET - this should encourage the development of compilers for other programming languages that target .NET. Second, it provides a guarantee that, if you restrict your classes to exposing only CLS-compliant features, code written in any other compliant language can use your classes.

The beauty of this idea is that the restriction to using CLS-compliant features applies only to public and protected members of classes and public classes. Within the private implementations of your classes, you can write whatever non-CLS code you want, because code in other assemblies (units of managed code, see later in this chapter) cannot access this part of your code anyway.

We won’t go into the details of the CLS specifications here. In general, the CLS won’t affect your C# code very much, because there are very few non-CLS-compliant features of C# anyway.

Garbage Collection

The garbage collector is .NET’s answer to memory management, and in particular to the question of what to do about reclaiming memory that running applications ask for. Up until now, two techniques have been used on the Windows platform for deallocating memory that processes have dynamically requested from the system:

  • Make the application code do it all manually.

  • Make objects maintain reference counts.

Having the application code responsible for deallocating memory is the technique used by lower-level, high-performance languages such as C++. It is efficient, and it has the advantage that (in general) resources are never occupied for longer than necessary. The big disadvantage, however, is the frequency of bugs. Code that requests memory also should explicitly inform the system when it no longer requires that memory. However, it is easy to overlook this, resulting in memory leaks.

Although modern developer environments do provide tools to assist in detecting memory leaks, they remain difficult bugs to track down, because they have no effect until so much memory has been leaked that Windows refuses to grant any more to the process. By this point, the entire computer may have appreciably slowed down due to the memory demands being made on it.

Maintaining reference counts is favored in COM. The idea is that each COM component maintains a count of how many clients are currently maintaining references to it. When this count falls to zero, the component can destroy itself and free up associated memory and resources. The problem with this is that it still relies on the good behavior of clients to notify the component that they have finished with it. It only takes one client not to do so, and the object sits in memory. In some ways, this is a potentially more serious problem than a simple C++-style memory leak, because the COM object may exist in its own process, which means that it will never be removed by the system (at least with C++ memory leaks, the system can reclaim all memory when the process terminates).

The .NET runtime relies on the garbage collector instead. This is a program whose purpose is to clean up memory. The idea is that all dynamically requested memory is allocated on the heap (that is true for all languages, although in the case of .NET, the CLR maintains its own managed heap for .NET applications to use). Every so often, when .NET detects that the managed heap for a given process is becoming full and therefore needs tidying up, it calls the garbage collector. The garbage collector runs through variables currently in scope in your code, examining references to objects stored on the heap to identify which ones are accessible from your code - that is to say which objects have references that refer to them. Any objects that are not referred to are deemed to be no longer accessible from your code and can therefore be removed. Java uses a system of garbage collection similar to this.

Garbage collection works in .NET because IL has been designed to facilitate the process. The principle requires that you cannot get references to existing objects other than by copying existing references and that IL be type safe. In this context, what we mean is that if any reference to an object exists, then there is sufficient information in the reference to exactly determine the type of the object.

It would not be possible to use the garbage collection mechanism with a language such as unmanaged C++, for example, because C++ allows pointers to be freely cast between types.

One important aspect of garbage collection is that it is not deterministic. In other words, you cannot guarantee when the garbage collector will be called; it will be called when the CLR decides that it is needed (unless you explicitly call the collector), though it is also possible to override this process and call up the garbage collector in your code.

Security

.NET can really excel in terms of complementing the security mechanisms provided by Windows because it can offer code-based security, whereas Windows only really offers role-based security.

Role-based security is based on the identity of the account under which the process is running (that is, who owns and is running the process). Code-based security, on the other hand, is based on what the code actually does and on how much the code is trusted. Thanks to the strong type safety of IL, the CLR is able to inspect code before running it in order to determine required security permissions. .NET also offers a mechanism by which code can indicate in advance what security permissions it will require to run.

The importance of code-based security is that it reduces the risks associated with running code of dubious origin (such as code that you’ve downloaded from the Internet). For example, even if code is running under the administrator account, it is possible to use code-based security to indicate that that code should still not be permitted to perform certain types of operation that the administrator account would normally be allowed to do, such as read or write to environment variables, read or write to the registry, or access the .NET reflection features.

Security issues are covered in more depth in Chapter 19, “.NET Security.”

Application Domains

Application domains are an important innovation in .NET and are designed to ease the overhead involved when running applications that need to be isolated from each other but that also need to be able to communicate with each other. The classic example of this is a Web server application, which may be simultaneously responding to a number of browser requests. It will, therefore, probably have a number of instances of the component responsible for servicing those requests running simultaneously.

In pre-.NET days, the choice would be between allowing those instances to share a process, with the resultant risk of a problem in one running instance bringing the whole Web site down, or isolating those instances in separate processes, with the associated performance overhead.

Up until now, the only means of isolating code has been through processes. When you start a new application, it runs within the context of a process. Windows isolates processes from each other through address spaces. The idea is that each process has available 4GB of virtual memory in which to store its data and executable code (4GB is for 32-bit systems; 64-bit systems use more memory). Windows imposes an extra level of indirection by which this virtual memory maps into a particular area of actual physical memory or disk space. Each process gets a different mapping, with no overlap between the actual physical memories that the blocks of virtual address space map to (see Figure 1-2).

Image from site
Figure 1-2

In general, any process is able to access memory only by specifying an address in virtual memory - processes do not have direct access to physical memory. Hence it is simply impossible for one process to access the memory allocated to another process. This provides an excellent guarantee that any badly behaved code will not be able to damage anything outside its own address space. (Note that on Windows 95/98, these safeguards are not quite as thorough as they are on Windows NT/2000/XP/2003, so the theoretical possibility exists of applications crashing Windows by writing to inappropriate memory.)

Processes don’t just serve as a way to isolate instances of running code from each other. On Windows NT/ 2000/XP/2003 systems, they also form the unit to which security privileges and permissions are assigned. Each process has its own security token, which indicates to Windows precisely what operations that process is permitted to do.

Although processes are great for security reasons, their big disadvantage is in the area of performance. Often, a number of processes will actually be working together, and therefore need to communicate with each other. The obvious example of this is where a process calls up a COM component, which is an executable and therefore is required to run in its own process. The same thing happens in COM when surrogates are used. Because processes cannot share any memory, a complex marshaling process has to be used to copy data between the processes. This results in a very significant performance hit. If you need components to work together and don’t want that performance hit, then you have to use DLL-based components and have everything running in the same address space - with the associated risk that a badly behaved component will bring everything else down.

Application domains are designed as a way of separating components without resulting in the performance problems associated with passing data between processes. The idea is that any one process is divided into a number of application domains. Each application domain roughly corresponds to a single application, and each thread of execution will be running in a particular application domain (see Figure 1-3).

Image from site
Figure 1-3

If different executables are running in the same process space, they are clearly able to easily share data, because theoretically they can directly see each other’s data. However, although this is possible in principle, the CLR makes sure that this does not happen in practice by inspecting the code for each running application, to ensure that the code cannot stray outside its own data areas. This looks at first sight like an almost impossible trick to pull off - after all, how can you tell what the program is going to do without actually running it?

In fact, it is usually possible to do this because of the strong type safety of the IL. In most cases, unless code is using unsafe features such as pointers, the data types it is using will ensure that memory is not accessed inappropriately. For example, .NET array types perform bounds checking to ensure that no out-of-bounds array operations are permitted. If a running application does need to communicate or share data with other applications running in different application domains, it must do so by calling on .NET’s remoting services.

Code that has been verified to check that it cannot access data outside its application domain (other than through the explicit remoting mechanism) is said to be memory type safe. Such code can safely be run along-side other type-safe code in different application domains within the same process.

Error Handling with Exceptions

The .NET Framework is designed to facilitate handling of error conditions using the same mechanism, based on exceptions, that is employed by Java and C++. C++ developers should note that because of IL’s stronger typing system, there is no performance penalty associated with the use of exceptions with IL in the way that there is in C++. Also, the finally block, which has long been on many C++ developers’ wish list, is supported by .NET and by C#.

Exceptions are covered in detail in Chapter 13, “Errors and Exceptions.” Briefly, the idea is that certain areas of code are designated as exception handler routines, with each one able to deal with a particular error condition (for example, a file not being found, or being denied permission to perform some operation). These conditions can be defined as narrowly or as widely as you want. The exception architecture ensures that when an error condition occurs, execution can immediately jump to the exception handler routine that is most specifically geared to handle the exception condition in question.

The architecture of exception handling also provides a convenient means to pass an object containing precise details of the exception condition to an exception handling routine. This object might include an appropriate message for the user and details of exactly where in the code the exception was detected.

Most exception-handling architecture, including the control of program flow when an exception occurs, is handled by the high-level languages (C#, Visual Basic 2009, C++), and is not supported by any special IL commands. C#, for example, handles exceptions using try{}, catch{}, and finally{} blocks of code. (For more details, see Chapter 13.)

What .NET does do, however, is provide the infrastructure to allow compilers that target .NET to support exception handling. In particular, it provides a set of .NET classes that can represent the exceptions, and the language interoperability to allow the thrown exception objects to be interpreted by the exception-handling code, irrespective of what language the exception-handling code is written in. This language independence is absent from both the C++ and Java implementations of exception handling, although it is present to a limited extent in the COM mechanism for handling errors, which involves returning error codes from methods and passing error objects around. The fact that exceptions are handled consistently in different languages is a crucial aspect of facilitating multilanguage development.

Use of Attributes

Attributes are a feature that is familiar to developers who use C++ to write COM components (through their use in Microsoft’s COM Interface Definition Language [IDL]). The initial idea of an attribute was that it provided extra information concerning some item in the program that could be used by the compiler.

Attributes are supported in .NET - and hence now by C++, C#, and Visual Basic 2009. What is, however, particularly innovative about attributes in .NET is that a mechanism exists whereby you can define your own custom attributes in your source code. These user-defined attributes will be placed with the metadata for the corresponding data types or methods. This can be useful for documentation purposes, where they can be used in conjunction with reflection technology in order to perform programming tasks based on attributes. Also, in common with the .NET philosophy of language independence, attributes can be defined in source code in one language and read by code that is written in another language.

Attributes are covered in Chapter 12, “Reflection.”


Previous PagePrevious
Next PageNext