类(class)是C#类型中最基础的类型。类是一个数据结构,将状态(字段)和行为(方法和其他函数成员)组合在一个单元中。类提供了用于动态创建类实例的定义,也就是对象(object)。类支持继承(inheritance)和多态(polymorphism),即派生类能够扩展和特殊化基类的机制。
使用类声明可以创建新的类。类声明以一个声明头开始,其组成方式如下:先是指定类的特性和修饰符,后跟类的名字,基类(如果有的话)的名字,以及被该类实现的接口名。声明头后面就是类体了,它由一组包含在大括号({})中的成员声明组成。
下面是一个名为Point的简单类的声明:
public class Point
{
public int x, y;
public Point(int x, int y){
this.x = x;
this.y = y;
}
}
使用new运算符创建类的实例,它将为新实例分配内存,调用构造函数初始化实例,并且返回对该实例的引用。下面的语句创建两个Point对象,并且将那些对象的引用保存到两个变量中:
Point p1 = new Point(0, 0);
Point p2 = new Point(10, 20);
当不再使用对象时,该对象所占的内存将被自动回收。在C#中,没有必要也不可能显式地释放对象。
1.6.1 成员
类的成员或者是静态成员(static member),或者是实例成员(instance member)。静态成员属于类,实例成员属于对象(类的实例)。
表1.6提供了类所能包含的各种成员的描述。
表1.6 类 的 成 员
成 员
描 述
常数
与类关联的常量值
字段
类的变量
方法
能够被类执行的计算和行为
属性
使对象能够读取和写入类的命名属性
索引器
使对象能够用与数组相同的方式进行索引
事件
能够被类产生的通知
运算符
类支持的转换和表达式运算符
构造函数
初始化类的实例或者类本身
析构函数
在永久销毁类的实例之前执行的行为
类型
被类声明的嵌套类型
1.6.2 可访问性
类的每个成员都有关联的可访问性,它控制能够访问该成员的程序文本区域。有5种可能的可访问性形式。表1.7概述了类的可访问性的意义。
表1.7 类的可访问性
可访问性
意 义
public
访问不受限制
protected
访问仅限于包含类或从包含类派生的类型
internal
访问仅限于当前程序集
protected internal
访问仅限于从包含类派生的当前程序集或类型
private
访问仅限于包含类
1.6.3 基类
类的声明可能通过在类名后加上冒号和基类的名字来指定一个基类译注4。省略基类等同于直接从object类派生。在下面的示例中,Point3D的基类是Point,而Point的基类是object:
public class Point
{
public int x, y;
public Point(int x, int y){
this.x = x;
this.y = y;
}
}
public class Point3D: Point
{
public int z;
public Point3D(int x, int y, int z): Point(x, y){
this.z = z;
}
}
Point3D类继承了其基类的成员。继承意味着类将隐式地包含其基类的所有成员(除了基类的构造函数)。派生类能够在继承基类的基础上增加新的成员,但是它不能移除继承成员的定义。在前面的示例中,Point3D类从Point类中继承了x字段和y字段,并且每一个Point3D实例都包含三个字段x,y和z。
从类类型到它的任何基类类型都存在隐式的转换。并且,类类型的变量能够引用该类的实例,或者任何派生类的实例。例如,对于前面给定的类声明,Point类型的变量能够引用Point实例或者Point3D实例:
Point a = new Point(10, 20);
Point b = new Point3D(10, 20, 30);
1.6.4 字段
字段是与对象或类相关联的变量。
当一个字段声明中含有static修饰符时,由该声明引入的字段为静态字段(static field)。它只标识了一个存储位置。不管创建了多少个类实例,静态字段都只会有一个副本。
当一个字段声明中不含有static修饰符时,由该声明引入的字段为实例字段(instance field)。类的每个实例都包含了该类的所有实例字段的一个单独副本。
在下面的示例中,Color类的每个实例都有r,g,b实例字段的不同副本,但是Black,White,Red,Green和Blue等静态字段只有一个副本:
public class Color
{
public static readonly Color Black = new Color(0, 0, 0);
public static readonly Color White = new Color(255, 255, 255);
public static readonly Color Red = new Color(255, 0, 0);
public static readonly Color Green = new Color(0, 255, 0);
public static readonly Color Blue = new Color(0, 0, 255);
private byte r, g, b;
public Color(byte r, byte g, byte b) {
this.r = r;
this.g = g;
this.b = b;
}
}
如前面的示例所示,通过readonly修饰符声明只读字段。给readonly字段的赋值只能作为声明的组成部分出现,或者在同一类中的实例构造函数或静态构造函数中出现。
1.6.5 方法
方法(method)是一种用于实现可以由对象或类执行的计算或操作的成员。静态方法(static method)只能通过类来访问。实例方法(instance method)则要通过类的实例访问。
方法有一个参数(parameter)列表(可能为空),表示传递给方法的值或者引用;方法还有返回类型(return type),用于指定由该方法计算和返回的值的类型。如果方法不返回一个值,则它的返回类型为void。
在声明方法的类中,该方法的签名必须是惟一的。方法的签名由它的名称、参数的数目、每个参数的修饰符和类型组成。返回类型不是方法签名的组成部分。
1.6.5.1 参数
参数用于将值或者引用变量传递给方法。当方法被调用时,方法的参数译注5从指定的自变量(argument)译注6得到它们实际的值。C#有4种参数:值参数、引用参数、输出参数和参数数组。
值参数(value parameter)用于输入参数的传递。值参数相当于一个局部变量,它的初始值是从为该参数所传递的自变量获得的。对值参数的修改不会影响所传递的自变量。
引用参数(reference parameter)用于输入和输出参数的传递。用于引用参数的自变量必须是一个变量,并且在方法执行期间,引用参数和作为自变量的变量所表示的是同一个存储位置。引用参数用ref修饰符声明。下面的示例展示了ref参数的使用:
using System;
class Test
{
static void Swap(ref int x, ref int y) {
int temp = x;
x = y;
y = temp;
}
static void Main() {
int i = 1, j = 2;
Swap(ref i, ref j);
Console.WriteLine("{0} {1}", i, j); //输出 "2 1"
}
}
输出参数(output parameter)用于输出参数的传递。输出参数类似于引用参数,不同之处在于调用方提供的自变量初始值无关紧要。输出参数用out修饰符声明。下面的示例展示了out参数的使用:
using System;
class Test {
static void Divide(int x, int y, out int result, out int remainder) {
result = x / y;
remainder = x % y;
}
static void Main() {
int res, rem;
Divide(10, 3, out res, out rem);
Console.WriteLine("{0} {1}", res, rem); //输出 "3 1"
}
}
参数数组(parameter array)允许将可变长度的自变量列表传递给方法。参数数组用params修饰符声明。只有方法的最后一个参数能够被声明为参数数组,而且它必须是一维数组类型。System.Console类的Write和WriteLine方法是参数数组应用的很好的例子。它们的声明形式如下:
public class Console
{
public static void Write(string fmt, params object[] args) {...}
public static void WriteLine(string fmt, params object[] args) {...}
...
}
在方法中使用参数数组时,参数数组表现得就像常规的数组类型参数一样。然而,带数组参数的方法调用中,既可以传递参数数组类型的单个自变量,也可以传递参数数组的元素类型的若干自变量。对于后者的情形,数组实例将自动被创建,并且通过给定的自变量初始化。示例:
Console.WriteLine("x={0} y={1} z={2}", x, y, z);
等价于下面的语句:
object[] args = new object[3];
args[0] = x;
args[1] = y;
args[2] = z;
Console.WriteLine("x={0}