img silver


发表于2001/1/2 1:12:00  745人阅读

Jeffrey Richter

For the past year or so, I've been focusing my attention on the Microsoft? .NET common language runtime platform. In my opinion, most new development will target this platform because it makes application development so much easier and faster. I also expect existing application development to move toward .NET at a rapid pace.


To help developers embrace this new platform, my next few columns will focus on various programming issues specific to .NET. I'll assume that you are already familiar with object-oriented programming concepts. Each column will focus on run-of-the-mill programming topics that are specific to the common language runtime. All .NET developers must become aware of these topics.


When showing code examples, I had to choose one of the many languages that support the .NET common language runtime. The most neutral language to choose would have been intermediate language (IL) assembly. However, it is unlikely that IL assembly will be the most popular language, so I've decided to use C#. C# is the new language that Microsoft designed specifically for the development of managed code. While the sample code shown is C# and the explanations are geared toward coding with C#, the concepts discussed are those exposed by the common language runtime and therefore apply to any language that targets it.


My goal is to introduce various programming topics and to give you some idea of how they're implemented. It is not my goal to fully describe each topic and all the nuances that surround it. For complete details on any topic presented, please refer to the common language runtime or language documentation. Now, with the introduction out of the way, let's begin….


True Object-oriented Design


For programmers using the Win32? SDK, access to most of the operating system features is through a set of standalone functions exported from DLLs. These standalone functions are very easy to call from non-object-oriented languages like C. However, it is quite daunting for new developers to face literally thousands of independent functions that, on the surface, seem unrelated. Making things more difficult is the fact that many functions start with the word Get (for example, GetCurrentProcess and GetStockObject). In addition, the Win32 API has evolved over the years and Microsoft has added new functions having similar semantics but offering slightly different features over the earlier functions. You can usually identify the newer functions because their names are similar to the original function's name (such as CreateWindow/CreateWindowEx, CreateTypeLib/CreateTypeLib2, and one of my personal favorites: CreatePen/CreatePenIndirect/ExtCreatePen).

对于使用Windows 2000 SDK 的程序员,都是通过一系列独立的从DLL引出的函数来访问大多数操作系统特性的。这些孤立函数在非面向对象语言如C都可以很容易地调用。然而,令新的开发者比较头疼的是要面对许多表面上看似独立的不相干的函数。但实际上许多函数之间有复杂的关系,比如有很多函数以单词Get(例如:GetCurrentProcess 和 GetStockObject)开头。另外,Win32API发展了很多年了,微软加了一些新函数,看上去名字很象但使用上同早期的函数有细微的差别。你可能经常会把一个新的函数看成和老的一样仅因为名字很象(比如:CreateWindow/CreateWindowEx, CreateTypeLib / CreateTypeLib2, CreatePen / CreatePenIndirect / ExtCreatePen)。

All of these issues have given programmers the impression that developing for Windows? is difficult. With .NET, Microsoft is finally addressing developers' cries for help by creating an entirely object-oriented platform. Platform services are now divided into individual namespaces (such as System.Collections, System.Data, System.IO, System.Security, System.Web, and so on), and each namespace contains a set of related class types that allow access to the platform's services.


Since class methods may be overloaded, methods that differ just slightly in their behavior are given identical names and differ only by their prototypes. For example, a class might offer three different versions of a CreatePen method. All methods do the same thing: create a pen. However, each method takes a different set of parameters and has slightly different behavior. In the future, should Microsoft need to create a fourth CreatePen method, the new method would fit seamlessly into the class as a first-class citizen.


Since all platform services are offered via this object-oriented paradigm, software developers should have some understanding of object-oriented programming. The object-oriented paradigm enables other features as well. For example, it is now easy to create specialized versions of the base class library's types using inheritance and polymorphism. Again, I strongly suggest you become familiar with these concepts, as they are now critical to working with the Microsoft .NET Framework.




In .NET, every object is ultimately derived from the System.Object type. This means that the following two type definitions (shown using C#) are identical:


class Jeff {




class Jeff : System.Object {



Since all object types are ultimately derived from System.Object, you are guaranteed that every object of every type has a minimum set of capabilities. The public methods available in the System.Object class are shown in Figure 1.


The common language runtime requires that all object instances be created using the new operator (which calls the newobj IL instruction). The following code shows how to create an instance of the Jeff type (declared previously):

通用语言运行库要求所有的对象都用 new算符创建(调用newobj IL指令)。下面的代码演示如何创建一个Jeff类型的的实例。

Jeff j = new Jeff("ConstructorParam1");

The new operator creates the object by allocating the number of bytes required for the specified type from the managed heap. It initializes the object's overhead members. Every object has some additional bytes that the common language runtime uses to manage the object, such as the object's virtual table pointer and a reference to a sync block.


The class type's constructor is called, passing it any parameters (the string "ConstructorParam1" in the earlier example) specified in the call to new. Note that most languages will compile constructors so that they call the base type's constructor; however, this is not required by the common language runtime.


After new has performed all of the operations I just mentioned, it returns a reference to the newly created object. In the example code, this reference is saved in the variable j, which is of type Jeff.

By the way, the new operator has no counterpart. That is, there is no way to explicitly free or destroy an object. The common language runtime offers a garbage-collected environment that automatically detects when objects are no longer being used or accessed and frees the objects automatically, which is a topic I plan to cover in an upcoming issue of MSDN? Magazine.


Casting Between Data Types


When programming, it is quite common to cast an object from one data type to another. In this section, I'll look at the rules that govern how objects are cast between data types. To start, look at the following line:


System.Object o = new Jeff("ConstructorParam1");

The previous line of code compiles and executes correctly because there is an implied cast. The new operator returns a reference to a Jeff type, but o is a reference to a System.Object type. Since all types (including the Jeff type) can be cast to System.Object, the implied cast is successful.

However, if you execute the following line, you get a compiler error since the compiler does not provide an implicit cast from a base type to a derived type.


Jeff j = o;

To get the command to compile, you must insert an explicit cast, as follows:


Jeff j = (Jeff) o;

Now the code compiles and executes successfully.

Let's look at another example:


System.Object o = new System.Object();

Jeff j = (Jeff) o;

On the first line, I have created an object of type System.Object. On the second line, I am attempting to convert a reference of type System.Object to a reference of type Jeff. Both lines of code compile just fine. However, when executed, the second line generates an InvalidCastException exception, which if not caught, forces the application to terminate.

When the second line of code executes, the common language runtime verifies that the object referred to by o is in fact an object of type Jeff (or any type derived from type Jeff). If so, the common language runtime allows the cast. However, if the object referenced by o has no relationship to Jeff, or is a base class of Jeff, then the common language runtime prevents the unsafe cast and raises the InvalidCastException exception.



C# offers another way to perform a cast using the as operator:

Jeff j = new Jeff(); // Create a new Jeff object创建一个新的Jeff对象

System.Object o = j as System.Object; // 转换成Object

// o now refers to the Jeff object o现在引用一个Jeff对象

The as operator attempts to cast an object to the specified type. However, unlike normal casting, the as operator will never throw an exception. Instead, if the object's type cannot be cast successfully, then the result is null. When the ill-cast reference is used, a NullReferenceException exception will be thrown. The following code demonstrates this concept.


System.Object o = new System.Object(); //Creates a new Object object

Jeff j = o as Jeff; //Casts o to a Jeff

// The cast above fails: no exception is raised but j is set to null

j.ToString(); // Accessing j generates a NullReferenceException

In addition to the as operator, C# also offers an is operator. The is operator checks whether an object instance is compatible with a given type and the result of the evaluation is either True or False. The is operator will never raise an exception.


System.Object o = new System.Object();

System.Boolean b1 = (o is System.Object); // b1 is True

System.Boolean b2 = (o is Jeff); // b2 is False

Note, if the object reference is null, the is operator always returns False since there is no object available to check its type.

To make sure you understand everything just presented, assume that the following two class definitions exist.


class B {

int x;


class D : B {

int x;


Now, check Figure 2 to see which lines would compile and execute successfully (ES), which would cause a compiler error (CE), and which would cause a common language runtime error (RE).


Assemblies and Namespaces

A collection of types can be grouped together into an assembly (a set of one or more files) and deployed. Within an assembly, individual namespaces can exist. To the application developer, namespaces look like logical groupings of related types. For example, the base class library assembly contains many namespaces. The System namespace includes core low-level types such as the Object base type, Byte, Int32, Exception, Math, and Delegate, while the System.Collections namespace includes such types as ArrayList, BitArray, Queue, and Stack.


同一类的类型可以被组合在一起形成一个集合。在集合里,允许存在独立的namespace。对于开发者,namespace就象一些相关类的逻辑上的组。例如,基础类库集合包括很多namespace。System namespace核心的低级类型如Object基类,Byte,Int32,Exception, Math, and Delegate,而System.Collections namespace包括这些类型ArrayList, BitArray, Queue, and Stack.。

To the compiler, a namespace is simply an easy way of making a type's name longer and ensuring uniqueness by preceding the name with some symbols separated by dots. To the compiler, the Object type in the System namespace really just identifies a type called System.Object. Similarly, the Queue type in the System.Collections namespace simply identifies a type called System.Collections.Queue.

对于编译器,namespace是保证类型的名字唯一的一种简单的方法,不同的名字和符号靠点来分开。一个Object类在System Namespace里可以写做System.Object。同样地,Queue类在System.Collections namespace里可以写做System.Collections.Queue。

The runtime engine doesn't know anything about namespaces. When you access a type, the common language runtime just needs to know the full name of the type and which assembly contains the definition of the type so that the common language runtime can load the correct assembly, find the type, and manipulate it.


Programmers usually want the most concise way of expressing their algorithms; it is extremely inconvenient to refer to every class type using its fully qualified name. For this reason, many programming languages offer a statement that instructs the compiler to try appending various prefixes to a type name until a match is made. When coding in C#, I usually place the following line at the top of my source code modules:


using System;

When I refer to a type in my code, the compiler needs to ensure that the type is defined and that my code accesses the type in the correct way. If the compiler can't find a type with the specified name, it tries appending "System." to the type name and checks if the generated name matches an existing type. The previous line of code allows me to use Object in my code, and the compiler will automatically expand the name to System.Object. I'm sure you can easily imagine how much typing this saves.


When checking for a type's definition, the compiler must know which assembly contains the type so that the assembly information and the type information can be emitted into the resulting file. To get the assembly information, you must pass the assembly that defines any referenced types to the compiler.


As you might imagine, there are some potential problems with this scheme. For programming convenience, you should avoid creating types that have conflicting names. However, in some cases, it is simply not possible. .NET encourages the reuse of components. Your application may take advantage of a component created by Microsoft and another component created by me. Both of these companies' components may offer a type called FooBar�Microsoft's FooBar does one thing and Richter's FooBar does something entirely different. In this scenario, you had no control over the naming of the class types. To reference the Microsoft FooBar, you'd use Microsoft.FooBar and to reference my FooBar, you'd use Richter.FooBar.


In the following code, the reference to FooBar is ambiguous. It might be nice if the compiler reported an error here, but the C# compiler actually just picks one of the possible FooBar types; you won't discover a problem until runtime:


using Microsoft;

using Richter;

class MyApp {

method void Hi() {

FooBar f = new FooBar(); // Ambiguous, compiler picks



To remove the ambiguity, you must explicitly tell the compiler which FooBar you want to create.


using Microsoft;

using Richter;

class MyApp {

method void Hi() {

Richter.FooBar f = new Richter.FooBar(); // Not ambiguous



There is another form of the using statement that allows you to create an alias for a single type. This is handy if you have just a few types that you use from a namespace and don't want to pollute the global namespace with all of a namespace's types. The following code demonstrates another way to solve the ambiguity problem.


// Define RichterFooBar symbol as an alias to Richter.FooBar

using RichterFooBar = Richter.FooBar;

class MyApp {

method void Hi() {

RichterFooBar f = new RichterFooBar(); // No error now



These methods of disambiguating a type are useful, but there are scenarios where this is still not good enough. Imagine that the Australian Boomerang Company (ABC) and the Alaskan Boat Corporation (ABC) are each creating a type, called BuyProduct, which they intend to ship in their respective assemblies. It is likely that both companies would create a namespace called ABC that contains a type called BuyProduct. Anyone who tries to develop an application that needs to buy both boomerangs and boats would be in for some trouble unless your programming language gives you a way to programmatically distinguish between the assemblies�not just between namespaces.

这种消除歧义的方法很有用,但还不够好。假如Australian Boomerang Company (ABC) and the Alaskan Boat Corporation (ABC)分别创建一个类型叫BuyProduct,并打算放在各自的集合里。两个公司都将创建包含BuyProduct类型的ABC namespace。如果某个开发者需要购买boomerangs 和boats就会产生麻烦,除非你的编程语言提供程序的方法来区别两个集合。

Unfortunately, the C# using statement only supports namespaces and does not offer any way to specify an assembly. However, in the real world, this problem does not come up very often, so it's rarely an issue. If you are designing component types that you expect third parties to use, it is highly recommended that you define these types in a namespace so compilers can easily disambiguate types. In fact, you should use your full company name (not an acronym) to be your top-level namespace name to reduce the likelihood of conflict. You can see that Microsoft uses a namespace of "Microsoft".


Creating a namespace is simply a matter of writing a namespace declaration into your code, as follows:


namespace CompanyName { // CompanyName

class A { // CompanyName.A

class B { ... } // CompanyName.A.B


namespace X { // CompanyName.X

class C { ... } // CompanyName.X.C



Note that namespaces are implicitly public. You cannot change this by including any access modifiers. However, you can define types within a namespace that are internal (can't be used outside the assembly) or public (can be accessed by any assembly). Namespaces denote logical containment only; accessibility or packaging is accomplished by placing the namespace into an assembly.


In my next column, I'll describe the different kinds of types that all .NET programmers must be aware of: primitive types, reference types, and value types. A good understanding of value types is extremely important to every .NET programmer.

0 0



取 消