本文PDF下载

类型基础

所有类型都从Syetem.Object派生

运行时要求每个类型最终都从System.Object类型派生。换言之,以下两个类型的定义是完全一致的:

隐式派生自Object

    public class Employee

    {

        public string Name { get; set; }

    }

显式派生自Object

    public class Employee : Object

    {

        public string Name { get; set; }

    }

由于所有类型最终都从System.Object派生,所以可以保证每个类型的每个对象都有一组最基本的方法。具体来说,System.Object类提供了如下的公共实例方法:

公共方法名称

说明

Equals

确定两个 Object 实例是否相等。

GetHashCode

用作特定类型的哈希函数。

ToString

返回表示当前 Object String

GetType

获取当前实例的 Type

此外,从System.Object派生的类型可以访问如下所示的受保护方法:

受保护方法名称

说明

MemberwiseClone

创建当前 Object 的浅表副本。

Finalize

允许 Object 在“垃圾回收”回收 Object 之前尝试释放资源并执行其他清理操作。

CLR要求所有的对象都用new关键字进行创建:

Employee employee = new Employee();

以下过程是new操作符所作的事:

1.         计算类型及其所有基类型中定义的所有实例字段所需要的字节数。

2.         从托管堆中分配指定类型所需的字节数,从而分配内存空间。

3.         初始化对象的“类型对象指针”(type object pointer)与“同步块索引成员”(sync block index)。

4.         调用实例的构造器,向其传入在对new的调用中指定的任何实参。

new执行了这些操作之后,会返回指向新建对象的一个引用。在前面的示例中,这个引用会保存到变量employee中,后者具有Employee类型。

因为Employee类型从Syetem.Object派生,所以Employee类型实例就具有了Syetem.Object定义的可访问的实例方法。以ToString()方法为例,它在默认情况下输入完整类型名称:

代码:

            Employee employee = new Employee();

            employee.Name = "Sunny D.D";

            Console.WriteLine(employee.ToString());

输出:

clip_image002

当然我们也可以重写ToString()方法,以获得想要的结果:

    public class Employee : Object

    {

        public string Name { get; set; }

        public override string ToString()

        {

            return Name;

        }

    }

clip_image004

 

类型转换

CLR最重要的特性之一就是类型安全性。在运行时,CLR总是可以调用GetType()方法得到一个对象的确切类型是什么。由于GetType()是非虚方法,所以一个类型不可能伪装成另一个类型。例如,Employee类型不能重写GetType()方法,从而返回一个String类型。即便你可以使用new关键字覆盖此方法,但CLR是在基类型Object上调用此方法的,所以仍然可以得到当前对象的确切类型:

    public class Employee : Object

    {

        public string Name { get; set; }

        public override string ToString()

        {

            return Name;

        }

        public new Type GetType()

        {

            return typeof(String);

        }

    }

调用:

            Employee employee = new Employee();

            Console.WriteLine(employee.GetType());

            object objEmployee = employee;

            Console.WriteLine(objEmployee.GetType());

结果:

clip_image006

在实际开发过程中,我们常需要将对象从一种类型转换为其他类型。CLR允许将一个对象转换为它的实际类型,或者它的任何基类型。

C#中,不需要任何特殊语法即可将一个对象转换为它的任何基类型,因为向基类型的转换被认为是一种安全的转换。然而,将对象转换为它的某个派生类型时,C#要求必须进行显式转换,因为这可能在运行时失败。以下代码演示了基类型与派生类之间的相互转换:

            object o = new Employee();

            Employee e = (Employee)o;

这个例子展示了你需要做什么,才能让编译器顺利的编译代码。接着,我将演示一个虽然可以通过编译,但会在运行时抛出一个InvalidCastException(因无效类型转换或显式转换引发的异常):

    public class Manager : Employee

    {

    }

调用:

            Employee e = null;

            object m = new Manager();

            e = (Employee)m;

            object d = new DateTime(1984, 10, 17);

            e = (Employee)d;

结果:

clip_image008

可以看到,在上述代码实际执行过程中,发生的第一次转换e = (Employee)m;成功了,这是由于m的类型Manager是从Employee派生的,所以CLR成功执行类型转换,继续往下进行。而在第二次转换e = (Employee)d;时,CLR抛出了一个异常,这是因为d的类型DateTime既不是一个Employee,也不是从Employee派生的任何类型,所以,CLR会禁止转型,并抛出异常。

如果CLR允许这样的转型,就无类型安全可言了,这会导致严重的后果——其中包括应用程序崩溃,以及利用伪装类型而造成的安全漏洞。因此,类型安全是CLR的一个极其重要的目标。

使用isas操作符进行转型

Is操作符检查对象是否与给定类型兼容,并返回一个Boolean值。注意,is操作符不会抛出异常,以下是演示内容:

            object o = new object();

            Console.WriteLine(o is Object);

            Console.WriteLine(o is Employee);

clip_image010

如果对象引用是nullis操作符总是返回false

is操作符通常以如下方式使用:

            if (o is Employee)

            {

                Employee e = (Employee)o;

            }

在这段代码中,CLR实际会检查两次对象的类型:首先if条件中核实o是否兼容于Employee,如果是,在if内部执行转型时,将再次核实o是否可以引用一个Employee。这无疑对性能造成一定的影响。由于这是一个相当常用的编程模式,所以C#专门提供了as操作符,目的就是简化此类代码的写法,同时提高性能:

            Employee e = o as Employee;

            if (e != null)

            {

            }

在这段代码中,CLR只进行了一次类型检查,核实o是否兼容于Employee类型,如果是,则返回对象的非null引用;如果不兼容,则返回null值。这样做可以提高代码性能。

as操作符的工作方式与强制类型转换一样,只是它不会抛出异常:如果转换失败则返回null值。在下表中,我将完成对ObjectEmployeeManager三种类型的类型转换测试:

语句

成功

编译时错误

运行时错误

Object o1 = new Object

 

 

Object o2 = new Employee()

 

 

Object o3 = new Manager()

 

 

Object o4 = o3

 

 

Employee e1 = new Employee()

 

 

Employee e2 = new Manager()

 

 

Manager m1 = new Manager()

 

 

Employee e3 = new Object()

 

 

Manager m2 = new Object()

 

 

Employee e4 = m1

 

 

Manager m3 = e2

 

 

Manager m4 = (Manager)m1

 

 

Manager m5 = (Manager)e2

 

 

Manager m6 = (Manager)e1

 

 

Employee e5 = (Employee)o1

 

 

Employee e6 = (Manager)e2

 

 

 

命名空间

命名空间是一种组织 C# 程序中出现的不同类型的方式。命名空间在概念上与计算机文件系统中的文件夹有些类似。与文件夹一样,命名空间可使类具有唯一的完全限定名称。一个 C# 程序包含一个或多个命名空间,每个命名空间或者由程序员定义,或者作为之前编写的类库的一部分定义。例如,System.Text命名空间中定义了一个处理一组字符串的类型:

System.Text.StringBuilder sb = new System.Text.StringBuilder();

很显然,这样的代码会显得十分繁琐,应该使用一种更为简洁的方式来直接引用System.Text.StringBuilder类型。C#编译器通过using指令来提供这个机制:

using System.Text;

 

StringBuilder sb = new StringBuilder();

编译器对待命名空间的方式存在一些潜在的问题:可能有两个或多个类型在不同的命名空间中具有相同的名称。在此,强烈建议大家为类型定义具有唯一性的名称。不过,发生类型名称重复的情况还是会经常发生的:

定义:

namespace 类型和成员基础

{

    public class CodeWorker : Employee

    {

    }

}

namespace SunnyCoder

{

    public class CodeWorker

    {

    }

}

调用:

CodeWorker c = new CodeWorker();

clip_image012

在这种情况下,为了消除歧义性,必须显示地告知编译器需要创建的是哪一个CodeWorker

SunnyCoder.CodeWorker c = new SunnyCoder.CodeWorker();

此外,C#using指令还支持另外一种形式,允许为一个类型或命名空间创建别名。如果只想使用某命名空间下的少数几个类型,不希望它的所有类型都跑出来“污染环境”,别名就显得十分方便:

using SunnyCoderCodeWorker = SunnyCoder.CodeWorker;

 

SunnyCoderCodeWorker c = new SunnyCoderCodeWorker();

以上这些消除类型歧义的方式都十分有效,但某些情况下需要更进一步。假设我们引入两个dll文件,他们中有一个同名的命名空间,且其中都包含一个同名的类型,这个时候就会遇到麻烦。好在C#编译器提供了一种叫做外部别名extern alias)的功能来解决这个问题。欲知详情,请参阅《C#语言规范》。

类型和成员基础

类型的各种成员

在一个类型中,可以定义0个或者多个以下种类的成员:

l  常量 常量是在编译时设置其值并且永远不能更改其值的字段。使用常量可以为特殊值提供有意义的名称以代替数字文本,以使代码变得更容易阅读及维护。定义常量请使用关键字const

l  字段 字段存储着类满足其设计所必须拥有的数据。例如,表示日历日期的类可能有三个整数字段:一个表示月份,一个表示日期,还有一个表示年份。强烈建议将字段声明为似有字段,防止类型的状态被该类型外部的代码破坏,外部访问字段应通过属性或方法来进行。

l  实例构造器 用于创建和初始化实例。创建新对象时将调用类构造函数。

l  类型构造器 类型构造器是 static方法,不能带任何参数。

l  方法 是通过指定访问级别、返回值、方法名称和任何方法参数在类或结构中声明的。这些部分统称为方法的“签名”。 方法参数括在括号中,并用逗号隔开。空括号表示方法不需要参数。作用于类型时,称为静态方法;作用于实例时,称为实例方法。方法一般会对类型或对象的字段执行读写操作。

l  操作符重载 它实际上是一个方法,定义了将一个特定的操作符作用于对象时,应当如何操作。

l  转换操作符 定义如何隐式或者显式地将对象从一种类型转换为另一种类型的方法。

l  属性 利用属性,可以使用一种简单的、字段风格的语法来设置或查询类型或对象的部分逻辑状态。它可以是没有参数的,也可以是有参数的。

l  事件 利用事件,可以向一个或多个静态或实例方法发送通知。事件包含两个方法,用于登记或者注销对该事件的关注(+=/-=)。事件通常使用一个委托类型来维护可登记的方法。

l  类型 类型可定义嵌套于其中的其他类型(内部类,嵌套类)。通常用这种方式将一个大且复杂的类型分解成较小的类型,以简化开发。

类型的可见性

在文件范围中定义类型时,可以将类型的可见性指定为public或者internalPublic类型不仅对它的定义程序集中的所有代码可见,并且还对其他数据集中的代码可见。internal类型仅对定义程序集中的所有代码可见。定义类型时,若不显式指定类型的可见性,C#编译器默认将类型的可见性设置为internal

如果对于程序集中的某种类型,不想让它对于所有程序集可见,仅对指定程序集可见的情况,请使用友元程序集定义方式。

成员的可访问性

定义类型的成员时,可指定成员的可访问性。在代码中引用一个成员时,成员的可访问性指出这种引用是否合法。下表中总结了5种可应用于成员的可访问性修饰符,它们按照限制的由小及大排列:

修饰符

描述

private

私有访问是允许的最低访问级别。私有成员只有在声明它们的类和结构体中才是可访问的。

protected

受保护成员在它的类中可访问并且可由派生类访问。

internal

只有在同一程序集的文件中,内部类型或成员才是可访问的。

protected internal

成员可由任何嵌套类型、任何派生类型(任何程序集内)、定义程序集中的任何方法都可以访问。

public

成员可由任何程序集的任何方法进行访问。

如果没有显式地声明成员的可访问性,编译器通常(但不是绝对)默认选择privateCLR要求接口类型的所有成员必须是public访问权限,所以不允许显式地指定接口成员的可访问性,编译器会自动将接口的所有成员的可访问性设置为public

静态类

静态类和类成员用于创建无需创建类的实例就能够访问的数据和函数。静态类成员可用于分离独立于任何对象标识的数据和行为:无论对象发生什么更改,这些数据和函数都不会随之变化。当类中没有依赖对象标识的数据或行为时,就可以使用静态类。

C#中,需要使用static关键字定义静态类。这个关键字只能用于类,而不能应用于结构(值类型),这是因为CLR总是允许值类型实例化。

C#编译器对静态类做出了如下限制:

l  静态类必须直接从基类System.Object派生。从其他基类派生没有任何意义,继承只适用于对象,而静态类无法创建实例。

l  静态类不能实现任何接口。这是因为只有实例才可以调用接口方法。

l  静态类只能定义静态成员(字段、方法、属性和事件)。

l  静态类不能作为字段、方法参数或者局部变量使用。

l  它们不能被实例化。

l  它们是密封的。

l  它们不能包含实例构造函数。

因此,创建静态类与创建仅包含静态成员和私有构造函数的类基本相同。私有构造函数阻止类被实例化。

使用静态类的优点在于,编译器能够执行检查以确保不致偶然地添加实例成员。编译器将保证不会创建此类的实利。

分部类

这个功能完全是由C#编译器提供的,CLR对于分部类是一无所知的。

partial这个关键字告诉C#编译器,一个类、结构或者接口的定义源码可能被分散到一个或多个文件中。主要有三方面原因促使我们使用它。

l  源代码控制 如果一个文件由多个开发人员进行编辑(通常是这样的),在他们编辑完成后需要合并各自的代码(svn的功能之一)。如果不想使用这种方式,可以把代码分散到多个文件中,然后开发人员各自编辑自己负责的那部分代码。

l  分解逻辑单元 可以将一个功能复杂的类型,分解为多个逻辑单元,从而简化开发,提高代码可读性。

l  代码拆分 常见于WebForm窗体、WinForm窗体。使用设计器拖拽代码时,VS会自动创建相应的代码。这样有助于提高开发效率,并避免如果人为的修改自动生成代码造成VS的异常。

 posted on 2011-02-20 12:48  Dawn.D  阅读(534)  评论(0编辑  收藏  举报