Improve Your Technology

Just another blog for techology

.NET Framework Overview

.NET Framework Conceptual Overview 

The .NET Framework is an integral Windows component that supports building and running the next generation of applications and XML Web services. The .NET Framework is designed to fulfill the following objectives:

·             To provide a consistent object-oriented programming environment whether object code is stored and executed locally, executed locally but Internet-distributed, or executed remotely.

·             To provide a code-execution environment that minimizes software deployment and versioning conflicts.

·             To provide a code-execution environment that promotes safe execution of code, including code created by an unknown or semi-trusted third party.

·             To provide a code-execution environment that eliminates the performance problems of scripted or interpreted environments.

·             To make the developer experience consistent across widely varying types of applications, such as Windows-based applications and Web-based applications.

·             To build all communication on industry standards to ensure that code based on the .NET Framework can integrate with any other code.

The .NET Framework has two main components: the common language runtime and the .NET Framework class library. The common language runtime is the foundation of the .NET Framework. You can think of the runtime as an agent that manages code at execution time, providing core services such as memory management, thread management, and remoting, while also enforcing strict type safety and other forms of code accuracy that promote security and robustness. In fact, the concept of code management is a fundamental principle of the runtime. Code that targets the runtime is known as managed code, while code that does not target the runtime is known as unmanaged code. The class library, the other main component of the .NET Framework, is a comprehensive, object-oriented collection of reusable types that you can use to develop applications ranging from traditional command-line or graphical user interface (GUI) applications to applications based on the latest innovations provided by ASP.NET, such as Web Forms and XML Web services.

The .NET Framework can be hosted by unmanaged components that load the common language runtime into their processes and initiate the execution of managed code, thereby creating a software environment that can exploit both managed and unmanaged features. The .NET Framework not only provides several runtime hosts, but also supports the development of third-party runtime hosts.

For example, ASP.NET hosts the runtime to provide a scalable, server-side environment for managed code. ASP.NET works directly with the runtime to enable ASP.NET applications and XML Web services, both of which are discussed later in this topic.

Internet Explorer is an example of an unmanaged application that hosts the runtime (in the form of a MIME type extension). Using Internet Explorer to host the runtime enables you to embed managed components or Windows Forms controls in HTML documents. Hosting the runtime in this way makes managed mobile code (similar to Microsoft® ActiveX® controls) possible, but with significant improvements that only managed code can offer, such as semi-trusted execution and isolated file storage.

The following illustration shows the relationship of the common language runtime and the class library to your applications and to the overall system. The illustration also shows how managed code operates within a larger architecture.

.NET Framework in context

 

The following sections describe the main components and features of the .NET Framework in greater detail.

Features of the Common Language Runtime

The common language runtime manages memory, thread execution, code execution, code safety verification, compilation, and other system services. These features are intrinsic to the managed code that runs on the common language runtime.

With regards to security, managed components are awarded varying degrees of trust, depending on a number of factors that include their origin (such as the Internet, enterprise network, or local computer). This means that a managed component might or might not be able to perform file-access operations, registry-access operations, or other sensitive functions, even if it is being used in the same active application.

The runtime enforces code access security. For example, users can trust that an executable embedded in a Web page can play an animation on screen or sing a song, but cannot access their personal data, file system, or network. The security features of the runtime thus enable legitimate Internet-deployed software to be exceptionally feature rich.

The runtime also enforces code robustness by implementing a strict type-and-code-verification infrastructure called the common type system (CTS). The CTS ensures that all managed code is self-describing. The various Microsoft and third-party language compilers generate managed code that conforms to the CTS. This means that managed code can consume other managed types and instances, while strictly enforcing type fidelity and type safety.

In addition, the managed environment of the runtime eliminates many common software issues. For example, the runtime automatically handles object layout and manages references to objects, releasing them when they are no longer being used. This automatic memory management resolves the two most common application errors, memory leaks and invalid memory references.

The runtime also accelerates developer productivity. For example, programmers can write applications in their development language of choice, yet take full advantage of the runtime, the class library, and components written in other languages by other developers. Any compiler vendor who chooses to target the runtime can do so. Language compilers that target the .NET Framework make the features of the .NET Framework available to existing code written in that language, greatly easing the migration process for existing applications.

While the runtime is designed for the software of the future, it also supports software of today and yesterday. Interoperability between managed and unmanaged code enables developers to continue to use necessary COM components and DLLs.

The runtime is designed to enhance performance. Although the common language runtime provides many standard runtime services, managed code is never interpreted. A feature called just-in-time (JIT) compiling enables all managed code to run in the native machine language of the system on which it is executing. Meanwhile, the memory manager removes the possibilities of fragmented memory and increases memory locality-of-reference to further increase performance.

Finally, the runtime can be hosted by high-performance, server-side applications, such as Microsoft® SQL Server™ and Internet Information Services (IIS). This infrastructure enables you to use managed code to write your business logic, while still enjoying the superior performance of the industry’s best enterprise servers that support runtime hosting.

.NET Framework Class Library

The .NET Framework class library is a collection of reusable types that tightly integrate with the common language runtime. The class library is object oriented, providing types from which your own managed code can derive functionality. This not only makes the .NET Framework types easy to use, but also reduces the time associated with learning new features of the .NET Framework. In addition, third-party components can integrate seamlessly with classes in the .NET Framework.

For example, the .NET Framework collection classes implement a set of interfaces that you can use to develop your own collection classes. Your collection classes will blend seamlessly with the classes in the .NET Framework.

As you would expect from an object-oriented class library, the .NET Framework types enable you to accomplish a range of common programming tasks, including tasks such as string management, data collection, database connectivity, and file access. In addition to these common tasks, the class library includes types that support a variety of specialized development scenarios. For example, you can use the .NET Framework to develop the following types of applications and services:

·             Console applications.

·             Windows GUI applications (Windows Forms).

·             ASP.NET applications.

·             XML Web services.

·             Windows services.

For example, the Windows Forms classes are a comprehensive set of reusable types that vastly simplify Windows GUI development. If you write an ASP.NET Web Form application, you can use the Web Forms classes.

Client Application Development

Client applications are the closest to a traditional style of application in Windows-based programming. These are the types of applications that display windows or forms on the desktop, enabling a user to perform a task. Client applications include applications such as word processors and spreadsheets, as well as custom business applications such as data-entry tools, reporting tools, and so on. Client applications usually employ windows, menus, buttons, and other GUI elements, and they likely access local resources such as the file system and peripherals such as printers.

Another kind of client application is the traditional ActiveX control (now replaced by the managed Windows Forms control) deployed over the Internet as a Web page. This application is much like other client applications: it is executed natively, has access to local resources, and includes graphical elements.

In the past, developers created such applications using C/C++ in conjunction with the Microsoft Foundation Classes (MFC) or with a rapid application development (RAD) environment such as Microsoft® Visual Basic®. The .NET Framework incorporates aspects of these existing products into a single, consistent development environment that drastically simplifies the development of client applications.

The Windows Forms classes contained in the .NET Framework are designed to be used for GUI development. You can easily create command windows, buttons, menus, toolbars, and other screen elements with the flexibility necessary to accommodate shifting business needs.

For example, the .NET Framework provides simple properties to adjust visual attributes associated with forms. In some cases the underlying operating system does not support changing these attributes directly, and in these cases the .NET Framework automatically recreates the forms. This is one of many ways in which the .NET Framework integrates the developer interface, making coding simpler and more consistent.

Unlike ActiveX controls, Windows Forms controls have semi-trusted access to a user’s computer. This means that binary or natively executing code can access some of the resources on the user’s system (such as GUI elements and limited file access) without being able to access or compromise other resources. Because of code access security, many applications that once needed to be installed on a user’s system can now be deployed through the Web. Your applications can implement the features of a local application while being deployed like a Web page.

Server Application Development

Server-side applications in the managed world are implemented through runtime hosts. Unmanaged applications host the common language runtime, which allows your custom managed code to control the behavior of the server. This model provides you with all the features of the common language runtime and class library while gaining the performance and scalability of the host server.

The following illustration shows a basic network schema with managed code running in different server environments. Servers such as IIS and SQL Server can perform standard operations while your application logic executes through the managed code.

Server-side managed code

 

ASP.NET is the hosting environment that enables developers to use the .NET Framework to target Web-based applications. However, ASP.NET is more than just a runtime host; it is a complete architecture for developing Web sites and Internet-distributed objects using managed code. Both Web Forms and XML Web services use IIS and ASP.NET as the publishing mechanism for applications, and both have a collection of supporting classes in the .NET Framework.

XML Web services, an important evolution in Web-based technology, are distributed, server-side application components similar to common Web sites. However, unlike Web-based applications, XML Web services components have no UI and are not targeted for browsers such as Internet Explorer and Netscape Navigator. Instead, XML Web services consist of reusable software components designed to be consumed by other applications, such as traditional client applications, Web-based applications, or even other XML Web services. As a result, XML Web services technology is rapidly moving application development and deployment into the highly distributed environment of the Internet.

If you have used earlier versions of ASP technology, you will immediately notice the improvements that ASP.NET and Web Forms offer. For example, you can develop Web Forms pages in any language that supports the .NET Framework. In addition, your code no longer needs to share the same file with your HTTP text (although it can continue to do so if you prefer). Web Forms pages execute in native machine language because, like any other managed application, they take full advantage of the runtime. In contrast, unmanaged ASP pages are always scripted and interpreted. ASP.NET pages are faster, more functional, and easier to develop than unmanaged ASP pages because they interact with the runtime like any managed application.

The .NET Framework also provides a collection of classes and tools to aid in development and consumption of XML Web services applications. XML Web services are built on standards such as SOAP (a remote procedure-call protocol), XML (an extensible data format), and WSDL ( the Web Services Description Language). The .NET Framework is built on these standards to promote interoperability with non-Microsoft solutions.

For example, the Web Services Description Language tool included with the .NET Framework SDK can query an XML Web service published on the Web, parse its WSDL description, and produce C# or Visual Basic source code that your application can use to become a client of the XML Web service. The source code can create classes derived from classes in the class library that handle all the underlying communication using SOAP and XML parsing. Although you can use the class library to consume XML Web services directly, the Web Services Description Language tool and the other tools contained in the SDK facilitate your development efforts with the .NET Framework.

If you develop and publish your own XML Web service, the .NET Framework provides a set of classes that conform to all the underlying communication standards, such as SOAP, WSDL, and XML. Using those classes enables you to focus on the logic of your service, without concerning yourself with the communications infrastructure required by distributed software development.

Finally, like Web Forms pages in the managed environment, your XML Web service will run with the speed of native machine language using the scalable communication of IIS.

Common Language Runtime Overview 

Compilers and tools expose the runtime’s functionality and enable you to write code that benefits from this managed execution environment. Code that you develop with a language compiler that targets the runtime is called managed code; it benefits from features such as cross-language integration, cross-language exception handling, enhanced security, versioning and deployment support, a simplified model for component interaction, and debugging and profiling services.

To enable the runtime to provide services to managed code, language compilers must emit metadata that describes the types, members, and references in your code. Metadata is stored with the code; every loadable common language runtime portable executable (PE) file contains metadata. The runtime uses metadata to locate and load classes, lay out instances in memory, resolve method invocations, generate native code, enforce security, and set run-time context boundaries.

The runtime automatically handles object layout and manages references to objects, releasing them when they are no longer being used. Objects whose lifetimes are managed in this way are called managed data. Garbage collection eliminates memory leaks as well as some other common programming errors. If your code is managed, you can use managed data, unmanaged data, or both managed and unmanaged data in your .NET Framework application. Because language compilers supply their own types, such as primitive types, you might not always know (or need to know) whether your data is being managed.

The common language runtime makes it easy to design components and applications whose objects interact across languages. Objects written in different languages can communicate with each other, and their behaviors can be tightly integrated. For example, you can define a class and then use a different language to derive a class from your original class or call a method on the original class. You can also pass an instance of a class to a method of a class written in a different language. This cross-language integration is possible because language compilers and tools that target the runtime use a common type system defined by the runtime, and they follow the runtime’s rules for defining new types, as well as for creating, using, persisting, and binding to types.

As part of their metadata, all managed components carry information about the components and resources they were built against. The runtime uses this information to ensure that your component or application has the specified versions of everything it needs, which makes your code less likely to break because of some unmet dependency. Registration information and state data are no longer stored in the registry where they can be difficult to establish and maintain. Rather, information about the types you define (and their dependencies) is stored with the code as metadata, making the tasks of component replication and removal much less complicated.

Language compilers and tools expose the runtime’s functionality in ways that are intended to be useful and intuitive to developers. This means that some features of the runtime might be more noticeable in one environment than in another. How you experience the runtime depends on which language compilers or tools you use. For example, if you are a Visual Basic developer, you might notice that with the common language runtime, the Visual Basic language has more object-oriented features than before. Following are some benefits of the runtime:

·         Performance improvements.

·         The ability to easily use components developed in other languages.

·         Extensible types provided by a class library.

·         New language features such as inheritance, interfaces, and overloading for object-oriented programming; support for explicit free threading that allows creation of multithreaded, scalable applications; support for structured exception handling and custom attributes.

If you use Microsoft® Visual C++® .NET, you can write managed code using Visual C++, which provides the benefits of a managed execution environment as well as access to powerful capabilities and expressive data types that you are familiar with. Additional runtime features include:

·         Cross-language integration, especially cross-language inheritance.

·         Garbage collection, which manages object lifetime so that reference counting is unnecessary.

·         Self-describing objects, which make using Interface Definition Language (IDL) unnecessary.

·         The ability to compile once and run on any CPU and operating system that supports the runtime.

You can also write managed code using the C# language, which provides the following benefits:

·         Complete object-oriented design.

·         Very strong type safety.

·         A good blend of Visual Basic simplicity and C++ power.

·         Garbage collection.

·         Syntax and keywords similar to C and C++.

·         Use of delegates rather than function pointers for increased type safety and security. Function pointers are available through the use of the unsafe C# keyword and the /unsafe option of the C# compiler (Csc.exe) for unmanaged code and data.

Memory Management:

Automatic Memory Management 

Automatic memory management is one of the services that the common language runtime provides during Managed Execution. The common language runtime’s garbage collector manages the allocation and release of memory for an application. For developers, this means that you do not have to write code to perform memory management tasks when you develop managed applications. Automatic memory management can eliminate common problems, such as forgetting to free an object and causing a memory leak, or attempting to access memory for an object that has already been freed. This section describes how the garbage collector allocates and releases memory.

Allocating Memory

When you initialize a new process, the runtime reserves a contiguous region of address space for the process. This reserved address space is called the managed heap. The managed heap maintains a pointer to the address where the next object in the heap will be allocated. Initially, this pointer is set to the managed heap’s base address. All reference types are allocated on the managed heap. When an application creates the first reference type, memory is allocated for the type at the base address of the managed heap. When the application creates the next object, the garbage collector allocates memory for it in the address space immediately following the first object. As long as address space is available, the garbage collector continues to allocate space for new objects in this manner.

Allocating memory from the managed heap is faster than unmanaged memory allocation. Because the runtime allocates memory for an object by adding a value to a pointer, it is almost as fast as allocating memory from the stack. In addition, because new objects that are allocated consecutively are stored contiguously in the managed heap, an application can access the objects very quickly.

Releasing Memory

The garbage collector’s optimizing engine determines the best time to perform a collection based on the allocations being made. When the garbage collector performs a collection, it releases the memory for objects that are no longer being used by the application. It determines which objects are no longer being used by examining the application’s roots. Every application has a set of roots. Each root either refers to an object on the managed heap or is set to null. An application’s roots include global and static object pointers, local variables and reference object parameters on a thread’s stack, and CPU registers. The garbage collector has access to the list of active roots that the just-in-time (JIT) compiler and the runtime maintain. Using this list, it examines an application’s roots, and in the process creates a graph that contains all the objects that are reachable from the roots.

Objects that are not in the graph are unreachable from the application’s roots. The garbage collector considers unreachable objects garbage and will release the memory allocated for them. During a collection, the garbage collector examines the managed heap, looking for the blocks of address space occupied by unreachable objects. As it discovers each unreachable object, it uses a memory-copying function to compact the reachable objects in memory, freeing up the blocks of address spaces allocated to unreachable objects. Once the memory for the reachable objects has been compacted, the garbage collector makes the necessary pointer corrections so that the application’s roots point to the objects in their new locations. It also positions the managed heap’s pointer after the last reachable object. Note that memory is compacted only if a collection discovers a significant number of unreachable objects. If all the objects in the managed heap survive a collection, then there is no need for memory compaction.

To improve performance, the runtime allocates memory for large objects in a separate heap. The garbage collector automatically releases the memory for large objects. However, to avoid moving large objects in memory, this memory is not compacted.

Generations and Performance

To optimize the performance of the garbage collector, the managed heap is divided into three generations: 0, 1, and 2. The runtime’s garbage collection algorithm is based on several generalizations that the computer software industry has discovered to be true by experimenting with garbage collection schemes. First, it is faster to compact the memory for a portion of the managed heap than for the entire managed heap. Secondly, newer objects will have shorter lifetimes and older objects will have longer lifetimes. Lastly, newer objects tend to be related to each other and accessed by the application around the same time.

The runtime’s garbage collector stores new objects in generation 0. Objects created early in the application’s lifetime that survive collections are promoted and stored in generations 1 and 2. The process of object promotion is described later in this topic. Because it is faster to compact a portion of the managed heap than the entire heap, this scheme allows the garbage collector to release the memory in a specific generation rather than release the memory for the entire managed heap each time it performs a collection.

In reality, the garbage collector performs a collection when generation 0 is full. If an application attempts to create a new object when generation 0 is full, the garbage collector discovers that there is no address space remaining in generation 0 to allocate for the object. The garbage collector performs a collection in an attempt to free address space in generation 0 for the object. The garbage collector starts by examining the objects in generation 0 rather than all objects in the managed heap. This is the most efficient approach, because new objects tend to have short lifetimes, and it is expected that many of the objects in generation 0 will no longer be in use by the application when a collection is performed. In addition, a collection of generation 0 alone often reclaims enough memory to allow the application to continue creating new objects.

After the garbage collector performs a collection of generation 0, it compacts the memory for the reachable objects as explained in Releasing Memory earlier in this topic. The garbage collector then promotes these objects and considers this portion of the managed heap generation 1. Because objects that survive collections tend to have longer lifetimes, it makes sense to promote them to a higher generation. As a result, the garbage collector does not have to reexamine the objects in generations 1 and 2 each time it performs a collection of generation 0.

After the garbage collector performs its first collection of generation 0 and promotes the reachable objects to generation 1, it considers the remainder of the managed heap generation 0. It continues to allocate memory for new objects in generation 0 until generation 0 is full and it is necessary to perform another collection. At this point, the garbage collector’s optimizing engine determines whether it is necessary to examine the objects in older generations. For example, if a collection of generation 0 does not reclaim enough memory for the application to successfully complete its attempt to create a new object, the garbage collector can perform a collection of generation 1, then generation 0. If this does not reclaim enough memory, the garbage collector can perform a collection of generations 2, 1, and 0. After each collection, the garbage collector compacts the reachable objects in generation 0 and promotes them to generation 1. Objects in generation 1 that survive collections are promoted to generation 2. Because the garbage collector supports only three generations, objects in generation 2 that survive a collection remain in generation 2 until they are determined to be unreachable in a future collection.

Releasing Memory for Unmanaged Resources

For the majority of the objects that your application creates, you can rely on the garbage collector to automatically perform the necessary memory management tasks. However, unmanaged resources require explicit cleanup. The most common type of unmanaged resource is an object that wraps an operating system resource, such as a file handle, window handle, or network connection. Although the garbage collector is able to track the lifetime of a managed object that encapsulates an unmanaged resource, it does not have specific knowledge about how to clean up the resource. When you create an object that encapsulates an unmanaged resource, it is recommended that you provide the necessary code to clean up the unmanaged resource in a public Dispose method. By providing a Dispose method, you enable users of your object to explicitly free its memory when they are finished with the object. When you use an object that encapsulates an unmanaged resource, you should be aware of Dispose and call it as necessary. For more information about cleaning up unmanaged resources and an example of a design pattern for implementing Dispose.

Compiling MSIL to Native Code 

Before you can run Microsoft intermediate language (MSIL), it must be converted by a .NET Framework just-in-time (JIT) compiler to native code, which is CPU-specific code that runs on the same computer architecture as the JIT compiler. Because the common language runtime supplies a JIT compiler for each supported CPU architecture, developers can write a set of MSIL that can be JIT-compiled and run on computers with different architectures. However, your managed code will run only on a specific operating system if it calls platform-specific native APIs, or a platform-specific class library.

JIT compilation takes into account the fact that some code might never get called during execution. Rather than using time and memory to convert all the MSIL in a portable executable (PE) file to native code, it converts the MSIL as needed during execution and stores the resulting native code so that it is accessible for subsequent calls. The loader creates and attaches a stub to each of a type’s methods when the type is loaded. On the initial call to the method, the stub passes control to the JIT compiler, which converts the MSIL for that method into native code and modifies the stub to direct execution to the location of the native code. Subsequent calls of the JIT-compiled method proceed directly to the native code that was previously generated, reducing the time it takes to JIT-compile and run the code.

The runtime supplies another mode of compilation called install-time code generation. The install-time code generation mode converts MSIL to native code just as the regular JIT compiler does, but it converts larger units of code at a time, storing the resulting native code for use when the assembly is subsequently loaded and run. When using install-time code generation, the entire assembly that is being installed is converted into native code, taking into account what is known about other assemblies that are already installed. The resulting file loads and starts more quickly than it would have if it were being converted to native code by the standard JIT option.

As part of compiling MSIL to native code, code must pass a verification process unless an administrator has established a security policy that allows code to bypass verification. Verification examines MSIL and metadata to find out whether the code is type safe, which means that it only accesses the memory locations it is authorized to access. Type safety helps isolate objects from each other and therefore helps protect them from inadvertent or malicious corruption. It also provides assurance that security restrictions on code can be reliably enforced.

The runtime relies on the fact that the following statements are true for code that is verifiably type safe:

·             A reference to a type is strictly compatible with the type being referenced.

·             Only appropriately defined operations are invoked on an object.

·             Identities are what they claim to be.

During the verification process, MSIL code is examined in an attempt to confirm that the code can access memory locations and call methods only through properly defined types. For example, code cannot allow an object’s fields to be accessed in a manner that allows memory locations to be overrun. Additionally, verification inspects code to determine whether the MSIL has been correctly generated, because incorrect MSIL can lead to a violation of the type safety rules. The verification process passes a well-defined set of type-safe code, and it passes only code that is type safe. However, some type-safe code might not pass verification because of limitations of the verification process, and some languages, by design, do not produce verifiably type-safe code. If type-safe code is required by security policy and the code does not pass verification, an exception is thrown when the code is run.


Common Type System 

The common type system defines how types are declared, used, and managed in the runtime, and is also an important part of the runtime’s support for cross-language integration. The common type system performs the following functions:

·             Establishes a framework that helps enable cross-language integration, type safety, and high performance code execution.

·             Provides an object-oriented model that supports the complete implementation of many programming languages.

·             Defines rules that languages must follow, which helps ensure that objects written in different languages can interact with each other.

 

Common Type System Overview 

This section describes concepts and defines terms that will help you understand and work with your language’s implementation of the common type system.

Classification of Types

The common type system supports two general categories of types, each of which is further divided into subcategories:

·             Value types

Value types directly contain their data, and instances of value types are either allocated on the stack or allocated inline in a structure. Value types can be built-in (implemented by the runtime), user-defined, or enumerations. For a list of built-in value types, see the .NET Framework Class Library.

·             Reference types

Reference types store a reference to the value’s memory address, and are allocated on the heap. Reference types can be self-describing types, pointer types, or interface types. The type of a reference type can be determined from values of self-describing types. Self-describing types are further split into arrays and class types. The class types are user-defined classes, boxed value types, and delegates.

Variables that are value types each have their own copy of the data, and therefore operations on one variable do not affect other variables. Variables that are reference types can refer to the same object; therefore, operations on one variable can affect the same object referred to by another variable.

All types derive from the System.Object base type.

The following example shows the difference between reference types and value types.

Visual Basic

Copy Code

Imports System Class Class1 Public Value As Integer = 0 End Class ‘Class1 Class Test Shared Sub Main() Dim val1 As Integer = 0 Dim val2 As Integer = val1 val2 = 123 Dim ref1 As New Class1() Dim ref2 As Class1 = ref1 ref2.Value = 123 Console.WriteLine(“Values: {0}, {1}”, val1, val2) Console.WriteLine(“Refs: {0}, {1}”, ref1.Value, ref2.Value) End Sub ‘Main End Class ‘Test

C#

Copy Code

using System; class Class1 { public int Value = 0; } class Test { static void Main() { int val1 = 0; int val2 = val1; val2 = 123; Class1 ref1 = new Class1(); Class1 ref2 = ref1; ref2.Value = 123; Console.WriteLine(“Values: {0}, {1}”, val1, val2); Console.WriteLine(“Refs: {0}, {1}”, ref1.Value, ref2.Value); } }

The output from this program is as follows.

Copy Code

Values: 0, 123 Refs: 123, 123

The following diagram illustrates how these various types are related. Note that instances of types can be simply value types or self-describing types, even though there are subcategories of these types.

Type classification

 

For more information about each type, see value types, enumerations, classes, delegates, arrays, interfaces, and pointers.

Values and Objects

Values are binary representations of data, and types provide a way of interpreting this data. A value type is stored directly as a binary representation of the type’s data. The value of a reference type is the location of the sequence of bits that represent the type’s data.

Every value has an exact type that completely defines the value’s representation and the operations that are defined on the value. Values of self-describing types are called objects. While it is always possible to determine the exact type of an object by examining its value, you cannot do so with a value type or pointer type. A value can have more than one type. A value of a type that implements an interface is also a value of that interface type. Likewise, a value of a type that derives from a base type is also a value of that base type.

Types and Assemblies

The runtime uses assemblies to locate and load types. The assembly manifest contains the information that the runtime uses to resolve all type references made within the scope of the assembly.

A type name in the runtime has two logical parts: the assembly name and the name of the type within the assembly. Two types with the same name but in different assemblies are defined as two distinct types.

Assemblies provide consistency between the scope of names seen by the developer and the scope of names seen by the runtime system. Developers author types in the context of an assembly. The content of the assembly a developer is building establishes the scope of names that will be available at run time.

Types and Namespaces

From the viewpoint of the runtime, a namespace is just a collection of type names. Particular languages might have constructs and corresponding syntax that help developers form logical groups of types, but these constructs are not used by the runtime when binding types. Thus, both the Object and String classes are part of the System namespace, but the runtime only recognizes the full names of each type, which are System.Object and System.String, respectively.

You can build a single assembly that exposes types that look like they come from two different hierarchical namespaces, such as System.Collections and System.Windows.Forms. You can also build two assemblies that both export types whose names contain MyDll.MyClass.

If you create a tool to represent types in an assembly as belonging to a hierarchical namespace, the tool must enumerate the types in an assembly or group of assemblies and parse the type names to derive a hierarchical relationship.

 

Type Definitions 

You define new types from existing types. Built-in value types, pointers, arrays, and delegates are defined when they are used and are referred to as implicit types. Types can be nested; that is, a type can be a member of another type.

A type definition includes:

·             Any attributes defined on the type.

·             The type’s visibility.

·             The type’s name.

·             The type’s base type.

·             Any interfaces implemented by the type.

·             Definitions for each of the type’s members.

Attributes

Attributes provide additional user-defined metadata. Attributes can be applied to almost any language element — types, properties, methods, and so on.

Type Accessibility

All types have an accessibility modifier that governs their accessibility from other types. The following table describes the type accessibilities supported by the runtime.

Accessibility

Description

public The type is accessible by all assemblies.
assembly The type is accessible only from within the assembly.

The accessibility of a nested type depends on its accessibility domain, which is determined by both the declared accessibility of the member and the accessibility domain of the immediately containing type. However, the accessibility domain of a nested type cannot exceed that of the containing type.

The accessibility domain of a nested member M declared in a type T within a program P is defined as follows (noting that M might itself be a type):

·             If the declared accessibility of M is public, the accessibility domain of M is the accessibility domain of T.

·             If the declared accessibility of M is protected internal, the accessibility domain of M is the intersection of the accessibility domain of T with the program text of P and the program text of any type derived from T declared outside P.

·             If the declared accessibility of M is protected, the accessibility domain of M is the intersection of the accessibility domain of T with the program text of T and any type derived from T.

·             If the declared accessibility of M is internal, the accessibility domain of M is the intersection of the accessibility domain of T with the program text of P.

·             If the declared accessibility of M is private, the accessibility domain of M is the program text of T.

Type Names

The common type system imposes only two restrictions on names:

1. All names are encoded as strings of Unicode (16-bit) characters.

2. Names are not permitted to have an embedded (16-bit) value of 0x0000.

All comparisons are done on a byte-by-byte basis, and are thus case-sensitive and locale-independent.

Although a type might reference types from other modules and assemblies, a type is fully defined within one module. Type names need only be unique within an assembly. To fully identify a type, the type name must be qualified by the assembly that contains the implementation of the type. For more information, see Specifying Fully Qualified Type Names.

Base Types and Interfaces

A type can inherit values and behaviors from another type. The common type system does not allow types to inherit from more than one base type.

A type can implement any number of interfaces. To implement an interface, a type must implement all the virtual members of that interface. A virtual method can be implemented by a derived type and can be invoked either statically or dynamically. For more information about virtual members, see Type Members. For more information about inheritance and interfaces, see Classes and Interfaces.

 

Type Members 

The runtime allows you to define members of your type: events, fields, nested types, methods, and properties. Each member has a signature. The following table describes the type members used in the .NET Framework.

Member

Description

Event Defines an incident that can be responded to, and defines methods for subscribing to, unsubscribing from, and raising the event. Events are often used to inform other types of state changes.
Field Describes and contains part of the type’s state. Fields can be of any type supported by the runtime.
Nested type Defines a type within the scope of the enclosing type.
Method Describes operations available on the type. A method’s signature specifies the allowable types of all its arguments and of its return value.A constructor is a special kind of method that creates new instances of a type.
Property Names a value or state of the type and defines methods for getting or setting the property’s value. Properties can be primitive types, collections of primitive types, user-defined types, or collections of user-defined types. Properties are often used to keep the public interface of a type independent from the type’s actual representation.

Member Characteristics

The common type system allows type members to have a variety of characteristics, but languages are not required to support all these characteristics. The following table describes these member characteristics.

Characteristic

Can apply to

Description

abstract Methods, properties, and events The type does not supply the method’s implementation. Types that inherit abstract methods and types that implement interfaces with abstract methods must supply an implementation for the method. The only exception is when the derived type is itself an abstract type. All abstract methods are virtual.
private, family, assembly, family and assembly, family or assembly, or public All Defines the accessibility of the member:

private

Accessible only from within the same type as the member or within a nested type.

family

Accessible from within the same type as the member and from derived types that inherit from it.

assembly

Accessible only in the assembly in which the type is defined.

family and assembly

Accessible only from types that qualify for both family and assembly access.

family or assembly

Accessible only from types that qualify for either family or assembly access.

public

Accessible from any type.

final Methods, properties, and events The virtual method cannot be overridden in a derived type.
initialize-only Fields The value can only be initialized, and cannot be written after initialization.
instance Fields, methods, properties, and events If a member is not marked as static (C# and C++), Shared (Visual Basic), virtual (C# and C++), or Overridable (Visual Basic), it is an instance member (there is no instance keyword). There will be as many copies of such members in memory as there are objects that use it.
literal Fields The value assigned to the field is a fixed value, known at compile time, of a built-in value type. Literal fields are sometimes referred to as constants.
newslot or override All Defines how the member interacts with inherited members with the same signature:

newslot

Hides inherited members with the same signature.

override

Replaces the definition of an inherited virtual method.

The default is newslot.

static Fields, methods, properties, and events The member belongs to the type it is defined on, not to a particular instance of the type; the member exists even if an instance of the type is not created, and it is shared among all instances of the type.
virtual Methods, properties, and events The method can be implemented by a derived type and can be invoked either statically or dynamically. If dynamic invocation is used, the type of the instance that makes the call at run time determines which implementation of the method is called, rather than the type known at compile time. To invoke a virtual method statically, the variable might need to be cast to a type that uses the desired version of the method.

Overloading

Each type member has a unique signature. Method signatures consist of the method name and a parameter list (the order and types of the method’s arguments). More than one method with the same name can be defined within a type as long as the signatures differ. When two or more methods with the same name are defined, the method is said to be overloaded. Parameter lists can be qualified by the varargs constraint, which indicates that the method supports a variable argument list. For example, in System.Char, IsDigit is overloaded. One method takes a Char and returns a Boolean. The other method takes a String and an Int32, and returns a Boolean.

Inheriting, Overriding, and Hiding Members

A derived type inherits all members of its base type; that is, these members are defined on and available to the derived type. The behavior or qualities of inherited members can be modified in two ways:

·             A derived type can hide an inherited member by defining a new member with the same signature. This might be done to make a previously public member private or to define new behavior for an inherited method that is marked as final.

·             A derived type can override an inherited virtual method. The overriding method provides a new definition of the method that will be invoked based on the type of the value at run time rather than the type of the variable known at compile time. A method can override a virtual method only if the virtual method is not marked as final and the new method is at least as accessible as the virtual method.

 

.NET Framework Developer’s Guide 

Value Types in the Common Type System 

Most programming languages provide built-in data types, such as integers and floating-point numbers that are copied when they are passed as arguments (that is, they are passed by value). In the .NET Framework, these are called value types. The runtime supports two kinds of value types:

·             Built-in value types

The .NET Framework defines built-in value types, such as System.Int32 and System.Boolean, which correspond and are identical to primitive data types used by programming languages.

·             User-defined value types

Your language will provide ways to define your own value types, which derive from System.ValueType or System.Enum. If you want to define a type representing a value that is small, such as a complex number (using two floating-point numbers), you might choose to define it as a value type because you can pass the value type efficiently by value. If the type you are defining would be more efficiently passed by reference, you should define it as a class instead.

For information specific to enumerations, see Enumerations in the Common Type System.

Value types are stored as efficiently as primitive types, yet you can call methods on them, including the virtual methods defined on the System.Object and System.ValueType classes, as well as any methods defined on the value type itself. You can create instances of value types, pass them as parameters, store them as local variables, or store them in a field of another value type or object. Value types do not have the overhead associated with storing an instance of a class and they do not require constructors.

For each value type, the runtime supplies a corresponding boxed type, which is a class that has the same state and behavior as the value type. Some languages require you to use special syntax when the boxed type is required; others automatically use the boxed type when it is needed. When you define a value type, you are defining both the boxed and the unboxed type.

Value types can have fields, properties, and events. They can also have static and nonstatic methods. When they are boxed, they inherit the virtual methods from System.ValueType, and they can implement zero or more interfaces.

Value types are sealed, which means that no other type can be derived from them. However, you can define virtual methods directly on the value type, and these methods can be called on either the boxed or unboxed form of the type. Although you cannot derive another type from a value type, you might define virtual methods on a value type when you are using a language in which it is more convenient to work with virtual methods than with nonvirtual or static methods.

The following example shows how to construct a value type for complex numbers.

Visual Basic

Copy Code

Option Strict Option Explicit Imports System ‘ Value type definition for a complex number representation. Public Structure Complex Public r, i As Double ‘ Constructor. Public Sub New(r As Double, i As Double) Me.r = r Me.i = i End Sub ‘ Returns one divided by the current value. Public ReadOnly Property Reciprocal() As Complex Get If r = 0.0 And i = 0.0 Then Throw New DivideByZeroException() End If Dim div As Double = r * r + i * i Return New Complex(r / div, -i / div) End Get End Property ‘ Conversion methods. Public Shared Function ToDouble(a As Complex) As Double Return a.r End Function Public Shared Function ToComplex(r As Double) As Complex Return New Complex(r, 0.0) End Function ‘ Basic unary methods. Public Shared Function ToPositive(a As Complex) As Complex Return a End Function Public Shared Function ToNegative(a As Complex) As Complex Return New Complex(-a.r, -a.i) End Function ‘ Basic binary methods for addition, subtraction, multiplication, and division. Public Shared Function Add(a As Complex, b As Complex) As Complex Return New Complex(a.r + b.r, a.i + b.i) End Function Public Shared Function Subtract(a As Complex, b As Complex) As Complex Return New Complex(a.r – b.r, a.i – b.i) End Function Public Shared Function Multiply(a As Complex, b As Complex) As Complex Return New Complex(a.r * b.r – a.i * b.i, a.r * b.i + a.i * b.r) End Function Public Shared Function Divide(a As Complex, b As Complex) As Complex Return Multiply(a, b.Reciprocal) End Function ‘ Override the ToString method so the value appears in write statements. Public Overrides Function ToString As String Return String.Format(“({0}+{1}i)”, r, i) End Function End Structure ‘ Entry point. Public Class ValueTypeSample Public Shared Sub Main() Dim a As New Complex(0, 1) Dim b As New Complex(0, – 2) Console.WriteLine() Console.WriteLine(“a = “ & a.ToString) Console.WriteLine(“b = “ & b.ToString) Console.WriteLine() Console.WriteLine(“a + b = “ & Complex.Add(a, b).ToString) Console.WriteLine(“a – b = “ & Complex.Subtract(a, b).ToString) Console.WriteLine(“a * b = “ & Complex.Multiply(a, b).ToString) Console.WriteLine(“a / b = “ & Complex.Divide(a, b).ToString) Console.WriteLine() Console.WriteLine(“(double)a = “ & Complex.ToDouble(a).ToString) Console.WriteLine(“(Complex)5 = “ & Complex.ToComplex(5).ToString) End Sub End Class

C#

Copy Code

using System; // Value type definition for a complex number representation. public struct Complex { public double r, i; // Constructor. public Complex(double r, double i) { this.r = r; this.i = i; } // Returns one divided by the current value. public Complex Reciprocal { get { if (r == 0d && i == 0d) throw new DivideByZeroException(); double div = r*r + i*i; return new Complex(r/div, -i/div); } } // Conversion operators. public static explicit operator double(Complex a) { return a.r; } public static implicit operator Complex(double r) { return new Complex(r,0d); } // Basic unary operators. public static Complex operator + (Complex a) { return a; } public static Complex operator – (Complex a) { return new Complex(-a.r, -a.i); } // Basic binary operators for addition, subtraction, multiplication, and division. public static Complex operator + (Complex a, Complex b) { return new Complex(a.r + b.r, a.i + b.i); } public static Complex operator – (Complex a, Complex b) { return new Complex(a.r – b.r, a.i – b.i); } public static Complex operator * (Complex a, Complex b) { return new Complex(a.r*b.r – a.i*b.i, a.r*b.i + a.i*b.r); } public static Complex operator / (Complex a, Complex b) { return a * b.Reciprocal; } // Override the ToString method so the value appears in write statements. public override string ToString() { return String.Format(“({0}+{1}i)”, r, i); } } // Entry point. public class ValueTypeSample { public static void Main() { Complex a = new Complex(0, 1); Complex b = new Complex(0, -2); Console.WriteLine(); Console.WriteLine(“a = ” + a); Console.WriteLine(“b = ” + b); Console.WriteLine(); Console.WriteLine(“a + b = ” + (a+b)); Console.WriteLine(“a – b = ” + (a-b)); Console.WriteLine(“a * b = ” + (a*b)); Console.WriteLine(“a / b = ” + (a/b)); Console.WriteLine(); Console.WriteLine(“(double)a = ” + (double)a); Console.WriteLine(“(Complex)5 = ” + (Complex)5); } }

The output from this program is as follows.

Copy Code

a = (0+1i) b = (0+-2i) a + b = (0+-1i) a – b = (0+3i) a * b = (2+0i) a / b = (-0.5+0i) (double)a = 0 (Complex)5 = (5+0i)

 

 

Classes in the Common Type System 

If you are familiar with object-oriented programming, you know that a class defines the operations an object can perform (methods, events, or properties) and defines a value that holds the state of the object (fields). Although a class generally includes both definition and implementation, it can have one or more members that have no implementation.

An instance of a class is an object. You access an object’s functionality by calling its methods and accessing its properties, events, and fields.

The following table provides a description of some of the characteristics that the runtime allows a class to have. (Additional characteristics that are available through Attribute classes are not included in this list.) Your language might not make all these characteristics available.

Characteristic

Description

sealed Specifies that another type cannot be derived from this type.
implements Indicates that the class uses one or more interfaces by providing implementations of interface members.
abstract Specifies that you cannot create an instance of the class. To use it, you must derive another class from it.
inherits Indicates that instances of the class can be used anywhere the base class is specified. A derived class that inherits from a base class can use the implementation of any virtual methods provided by the base class, or the derived class can override them with its own implementation.
exported or not exported Indicates whether a class is visible outside the assembly in which it is defined. Applies only to top-level classes.

Nested classes also have member characteristics. For more information, see Type Members.

Class members that have no implementation are abstract members. A class that has one or more abstract members is itself abstract; new instances of it cannot be created. Some languages that target the runtime allow you to mark a class as abstract even if none of its members are abstract. You can use an abstract class when you need to encapsulate a basic set of functionality that derived classes can inherit or override when appropriate. Classes that are not abstract are referred to as concrete classes.

A class can implement any number of interfaces, but it can inherit from only one base class. All classes must have at least one constructor, which initializes new instances of the class.

Each language that supports the runtime provides a way to indicate that a class or class member has specific characteristics. When you use the syntax required by your language, the language ensures that the characteristics of the class and its members are stored (as metadata) along with the implementation of the class.

 

 

Delegates in the Common Type System 

The runtime supports reference types called delegates that serve a purpose similar to that of function pointers in C++. Unlike function pointers, delegates are secure, verifiable, and type safe. A delegate type can represent any method with a compatible signature. While function pointers can only represent static functions, a delegate can represent both static and instance methods. Delegates are used for event handlers and callback functions in the .NET Framework.

Note

The common language runtime does not support serialization of global methods, so delegates cannot be used to execute global methods in other application domains.

All delegates inherit from MulticastDelegate, which inherits from Delegate. The C#, Visual Basic, and C++ languages do not allow inheritance from these types, instead providing keywords for declaring delegates.

Because delegates inherit from MulticastDelegate, a delegate has an invocation list, which is a list of methods that the delegate represents and that are executed when the delegate is invoked. All methods in the list receive the arguments supplied when the delegate is invoked.

Note

The return value is not defined for a delegate that has more than one method in its invocation list, even if the delegate has a return type.

Creating and Using Delegates

In many cases, such as callback methods, a delegate represents only one method, and the only actions you need to take are creating the delegate and invoking it.

For delegates that represent multiple methods, the .NET Framework provides methods of the Delegate and MulticastDelegate delegate classes to support operations such as adding a method to a delegate’s invocation list (the System.Delegate.Combine(System.Delegate[]) method), removing a method (the System.Delegate.Remove(System.Delegate,System.Delegate) method), and getting the invocation list (the System.Delegate.GetInvocationList method).

Note

It is not necessary to use these methods for event-handler delegates in C#, C++, and Visual Basic, as these languages provide syntax for adding and removing event handlers.

Closed Static Delegates and Open Instance Delegates

Delegates can represent static (Shared in Visual Basic) or instance methods. Usually when a delegate represents an instance method, the instance is bound to the delegate along with the method. For example, an event-handler delegate might have three instance methods in its invocation list, each with a reference to the object the method belongs to.

In the .NET Framework version 2.0, it is also possible to create an open delegate for an instance method. An instance method has an implicit instance parameter (represented by this in C# or Me in Visual Basic), and it can be represented by a delegate type that exposes this hidden parameter. That is, the delegate type must have an extra parameter at the beginning of its formal parameter list, of the same type as the class the method belongs to. The converse of this scenario is also supported, so that it is possible to bind the first argument of a static method.

Note

The creation of open instance and closed static delegates is not directly supported by Visual Basic, C++, or C# for delegate constructors. Instead, use one of the System.Delegate.CreateDelegate method overloads that specifies MethodInfo objects, such as System.Delegate.CreateDelegate(System.Type,System.Object,System.Reflection.MethodInfo,System.Boolean).

Relaxed Rules for Delegate Binding

In the .NET Framework version 2.0, the parameter types and return type of a delegate must be compatible with the parameter types and return type of the method the delegate represents; the types do not have to match exactly.

Note

In the .NET Framework versions 1.0 and 1.1, the types must match exactly.

A parameter of a delegate is compatible with the corresponding parameter of a method if the type of the delegate parameter is more restrictive than the type of the method parameter, because this guarantees that an argument passed to the delegate can be passed safely to the method.

Similarly, the return type of a delegate is compatible with the return type of a method if the return type of the method is more restrictive than the return type of the delegate, because this guarantees that the return value of the method can be cast safely to the return type of the delegate.

For example, a delegate with a parameter of type Hashtable and a return type of Object can represent a method with a parameter of type Object and a return value of type Hashtable.

For more information and example code, see System.Delegate.CreateDelegate(System.Type,System.Object,System.Reflection.MethodInfo).

Delegates and Asynchronous Method Calls

Every delegate has a BeginInvoke method that allows you to call the delegate asynchronously, and an EndInvoke method that cleans up resources afterward. These methods are generated automatically for each delegate type. When a delegate is invoked by using the BeginInvoke method, the method the delegate represents is executed on a thread belonging to the ThreadPool.

 

Arrays in the Common Type System 

An array type is defined by specifying the element type of the array, the rank (number of dimensions) of the array, and the upper and lower bounds of each dimension of the array. All these are included in any signature of an array type, although they might be marked as dynamically (rather than statically) supplied. Exact array types are created automatically by the runtime as they are required, and no separate definition of the array type is needed. Arrays of a given type can only hold elements of that type. For more information on the type of a value, see the section “Values and Objects” in Common Type System Overview.

Values of an array type are objects. Array objects are defined as a series of locations where values of the array element type are stored. The number of repeated values is determined by the rank and bounds of the array.

Array types inherit from the type System.Array. This class represents all arrays regardless of the type of their elements or their rank. The operations defined on arrays are: allocating an array based on size and lower bound information; indexing an array to read and write a value; computing the address of an element of an array (a managed pointer); and querying for the rank, bounds, and total number of values stored in an array.

Arrays of one dimension with a zero lower bound for their elements (sometimes called vectors) have a type based on the type of the elements in the array, regardless of the upper bound. Arrays with more than one dimension, or with one dimension but a nonzero lower bound, have the same type if they have the same element type and rank, regardless of the lower bound on the array. Zero-dimensional arrays are not supported.

 

 

Interfaces in the Common Type System 

Interfaces can have static members, nested types, and abstract, virtual members, properties, and events. Any class implementing an interface must supply definitions for the abstract members declared in the interface. An interface can require that any implementing class must also implement one or more other interfaces.

The following restrictions apply to interfaces:

·             An interface can be declared with any accessibility, but interface members must all have public accessibility.

·             No security permissions can be attached to members or to the interface itself.

·             Interfaces cannot define constructors.

Each language must provide rules for mapping an implementation to the interface that requires the member, as more than one interface can declare a member with the same signature and these members can have separate implementations.

 

Pointers in the Common Type System 

Pointers are special kinds of variables. There are three kinds of pointers supported by the runtime: managed pointers, unmanaged pointers, and unmanaged function pointers.

A managed pointer, also known as a handle to an object on the managed heap, is a new type of pointer available to managed applications. Managed pointers are references to a managed block of memory from the common language runtime heap. Automatic garbage collection is performed on this heap. Managed pointers are generated for method arguments that are passed by reference. Some languages provide other ways of generating managed pointers. Only managed pointers are CLS-compliant.

Note

In Visual C++ 2002 and Visual C++ 2003, __gc * was used to declare a managed pointer. This is replaced with a ^ in Visual C++ 2005, for example ArrayList^ al = gcnew ArrayList();.

An unmanaged pointer is the traditional C++ pointer to an unmanaged block of memory from the standard C++ heap. Because unmanaged pointers are not part of the Common Language Specification (CLS), your language might not provide syntax to define or access these types. See the documentation for your language for information on support for unmanaged pointers.

An unmanaged function pointer is also a traditional C++ pointer that refers to the address of a function. The CLS provides delegates as a managed alternative to unmanaged function pointers.

An explicit definition of a pointer type is not required. All the information necessary to determine the type of a pointer is present when the pointer is declared.

While pointer types are reference types, the value of a pointer type is not an object and you cannot determine the exact type from such a value.

The common type system provides two type-safe operations on pointer types: loading a value from and writing a value to the location referenced by the pointer. These type-safe operations are CLS-compliant.

The common type system also provides three byte-based address arithmetic operations on pointer types: adding integers to and subtracting integers from pointers, and subtracting one pointer from another. The results of the first two arithmetic operations return a value of the same type as the original pointer. These byte-based operations are not CLS-complian.

 

Advertisements

July 4, 2008 - Posted by | Framework, Technology | ,

No comments yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: