.NET新平台编程
.NET新平台编程 .NET新平台编程
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.
在过去的一年里,我一直注意微软的.NET通用语言运行库平台。在我看来,大多数新的开发将转到这个平台,因为他将使开发变得非常简单而快速。我也希望现有的开发也尽快转移到.NET。
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.
为了帮助开发者理解这个新平台,我的下几个专栏将着重讲述各种.NET编程问题。我假定你已经熟悉面向对象编程概念。每个专栏将针对一般的通用语言运行库的编程问题。所有的开发者必须了解这些问题。
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.
在给出编程示例的时候,我需要从.NET通用语言运行库支持的编程语言里来选择一种。最应该选择的是中间汇编语言。然而,遗憾的是它不是最流行的语言,所以我选择C#。C#是一种新语言,微软专门为了开发受控制代码而设计的。虽然示例是用C#的,并且解释也是针对C#的,但讨论的概念却是适合所有使用通用语言运行库的编程语言的。
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.
所有的这些问题给从事Windows开发的人留下的深刻印象是难。使用.NET,微软创造了一个完全的面向对象平台,终于解决了困扰开发者的问题。平台服务现在被分成了各个对立的Namespace(这个词很难准确翻译,还是用英文写吧)(如:系统,集合,系统数据,系统IO,系统安全,系统Web,等等),并且每个Namespace包含一系列的相关类用来访问平台服务。
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.
因为类方法可能被重载,同名的方法的参数不同可能作用也有些不同。例如,一个类有三个不同的版本的CreatePen方法。所有的方法做同样的事情,创建一个笔。然而,每个方法有不同的参数和行为上的细微差别。将来,微软可能要创建第四个CreatePen方法,新方法将成为这个类的成员。
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.
因为所有的平台服务都是通过面向对象方式提供的,软件开发者应该对面向对象有一些了解。面向对象有很多优点。例如,很容易使用继承和多态创建一个基础类库的特别版本。我再次强烈建议熟悉这些概念,因为这些和微软的.NET构架紧密相关。
System.Object
系统对象
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:
在.NET,每个对象都从System.Object派生。着意味着下列两个类型定义是一样的。
class Jeff {
???
}
and
和
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.
因为所有的对象类型都从System.Object派生,这样保证了每个类型的对象有一系列最基本的功能。System.Object的公有方法如图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.
New算符从受控制的堆里分配指定类型需要的内存来创建对象。它初始化对象成员。每个对象都有一些附加字节,通用语言运行库用来管理对象,如对象的虚表指针和同步锁定引用。
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.
在new完成了我所说的所有操作之后,它返回一个新创建的对象的引用。在例子里,引用被保存到变量j里,j的类型是Jeff。顺便说一下,new算符没有一个对应的算符,也就是说,没有可以明确的释放或注销一个对象的方法。通用语言运行库提供一个垃圾回收环境,能自动检测,如果对象不再使用或被访问将会被自动删除。这就是我将在接下来的MSDN杂志里讨论的主题。
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.
这行程序可以正确编译运行,因为这是隐式类型转换。New算符返回Jeff类型的引用,但o是一个System.Object类型的。因为所有的类型都从System.object派生,所以都可以转换成System.Object类型。然而如果你执行以下这行,就会产生编译错误,不能做从基类到派生类的隐式转换。
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.
在第一行,我创建一个对象,类型是System.Object,在第二行,我试图把System.Object类型的引用转换成Jeff类型的。两行代码都可以正确编译。然而,在运行时,第二行代码会产生一个InvalidCastException,如果没有捕获这个异常,那么程序将终止。
当第二行代码运行时,通用语言运行库验证对象o引用的类型是否是一个Jeff类型,如果是,通用语言运行库允许转换。如果o引用的对象和Jeff没有关系或者是Jeff的基类,那么通用语言运行库将禁止这种不安全的转换,并且产生一个异常。
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.
As算符用来尝试把对象转换成指定类型。然而,不同于普通的转换,as算符不会抛出异常。而且,如果对象的类型不能成功转换将返回NULL。当使用错误的引用时,将抛出NullReferenceException这个异常。下面的代码演示了这个概念。
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.
为了配合as算符,C#还提供一个is算符。Is算符检查一个对象是否是某个类型的,返回值是TRUE或者FALSE。Is算符不会产生异常。
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.
注意,如果对象引用为NULL,is算符回返回false,因为没有对象可供检查类型。
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).
请看图2,哪一行将正确编译运行,哪一行将产生编译错误,哪一行将产生运行错误。
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.
集合和Namespaces
同一类的类型可以被组合在一起形成一个集合。在集合里,允许存在独立的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.
运行引擎并不知道有namespace,当你访问一个类型,通用语言运行库仅需要知道类型的全称和在哪个集合里有这个类型的定义,以便通用语言运行库可以正确加载查找和操作。
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:
程序员通常希望以最简单的方法表达算式,这样用全称引用类就比较困难。因此,许多程序语言提供一种指令,可以让编译器来添加前缀。在用C#时,我通常在程序的开头加上下面这段语句。
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.
当我在代码里引用某个类型时,编译器需要得到这个类型的定义并且保证我的代码可以正确访问这个类型。如果编译器用名字不能找到类型的定义,它会尝试加上”system”前缀再查找。前面这行代码使我可以访问Object类,编译器自动把名字变成System.Object。可想而知这样会少打很多字。
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.
可以想象,这样做会有一些潜在的问题。为了编程方便,你应该尽量避免使用可能造成冲突的名字。然而,某些情况下,简直不可能。.NET主张组件重用。你的程序可能用到微软的组件和我的另一个组件。两个组件都叫FootBar,但是两个组件的功能完全不同。这种情况下你不能仅通过类型名来控制对象,引用微软的FootBar你要用Microsoft.FootBar,引用我的FootBar要用Richter.FootBar。
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:
下面的代码里,FootBar的引用是不明确的。编译器在这里最好能保错,但是C#编译器会选一个可用的类型,知道运行时你才会发现有问题。
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.
为了去掉不明确的引用,你必须明确地告诉编译器你到底要创建哪个FootBar。
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.
另一种方式是为简单类型创建一个别名。如果你只从某个namespace里引用少数几个类型,而又不想影响所有的namespace类型,这样比较简单。下面的代码演示如何解决不明确引用的问题。
// 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".
不幸的是,C#指令仅支持Namespace而不提供区别集合的方法。然而在现实世界里这个问题不经常发生。但它的确是个问题。如果你设计的组件希望给第三方使用,推荐你使用namespace来定义类型,以便编译器可以区分类型。实际上,你应该用公司全名做顶级namespace来减少冲突的可能性。微软使用”Microsoft”
Creating a namespace is simply a matter of writing a namespace declaration into your code, as follows:
创建namespce和简单,如下所示来写namespace的声明。
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.
注意,namespace却省是公有的。不能靠任何访问修饰来修改。你可以在namespace里来定义内部的和外部的类型,namespace表示逻辑上的包含。
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.
下一个专栏,我会讲述不同类型的.Net开发者都必须了解的原始类型,引用类型和值类型。透彻理解值类型对每个.NET开发者尤其重要