類(lèi)(class)是C#類(lèi)型中最基礎(chǔ)的類(lèi)型。類(lèi)是一個(gè)數(shù)據(jù)結(jié)構(gòu),將狀態(tài)(字段)和行為(方法和其他函數(shù)成員)組合在一個(gè)單元中。類(lèi)提供了用于動(dòng)態(tài)創(chuàng)建類(lèi)實(shí)例的定義,也就是對(duì)象(object)。類(lèi)支持繼承(inheritance)和多態(tài)(polymorphism),即派生類(lèi)能夠擴(kuò)展和特殊化基類(lèi)的機(jī)制。
使用類(lèi)聲明可以創(chuàng)建新的類(lèi)。類(lèi)聲明以一個(gè)聲明頭開(kāi)始,其組成方式如下:先是指定類(lèi)的特性和修飾符,后跟類(lèi)的名字,基類(lèi)(如果有的話)的名字,以及被該類(lèi)實(shí)現(xiàn)的接口名。聲明頭后面就是類(lèi)體了,它由一組包含在大括號(hào)({})中的成員聲明組成。
下面是一個(gè)名為Point的簡(jiǎn)單類(lèi)的聲明:
public class Point
{
public int x, y;
public Point(int x, int y){
this.x = x;
this.y = y;
}
}
使用new運(yùn)算符創(chuàng)建類(lèi)的實(shí)例,它將為新實(shí)例分配內(nèi)存,調(diào)用構(gòu)造函數(shù)初始化實(shí)例,并且返回對(duì)該實(shí)例的引用。下面的語(yǔ)句創(chuàng)建兩個(gè)Point對(duì)象,并且將那些對(duì)象的引用保存到兩個(gè)變量中:
Point p1 = new Point(0, 0);
Point p2 = new Point(10, 20);
當(dāng)不再使用對(duì)象時(shí),該對(duì)象所占的內(nèi)存將被自動(dòng)回收。在C#中,沒(méi)有必要也不可能顯式地釋放對(duì)象。
1.6.1 成員
類(lèi)的成員或者是靜態(tài)成員(static member),或者是實(shí)例成員(instance member)。靜態(tài)成員屬于類(lèi),實(shí)例成員屬于對(duì)象(類(lèi)的實(shí)例)。
表1.6提供了類(lèi)所能包含的各種成員的描述。
表1.6 類(lèi) 的 成 員
成 員
描 述
常數(shù)
與類(lèi)關(guān)聯(lián)的常量值
字段
類(lèi)的變量
方法
能夠被類(lèi)執(zhí)行的計(jì)算和行為
屬性
使對(duì)象能夠讀取和寫(xiě)入類(lèi)的命名屬性
索引器
使對(duì)象能夠用與數(shù)組相同的方式進(jìn)行索引
事件
能夠被類(lèi)產(chǎn)生的通知
運(yùn)算符
類(lèi)支持的轉(zhuǎn)換和表達(dá)式運(yùn)算符
構(gòu)造函數(shù)
初始化類(lèi)的實(shí)例或者類(lèi)本身
析構(gòu)函數(shù)
在永久銷(xiāo)毀類(lèi)的實(shí)例之前執(zhí)行的行為
類(lèi)型
被類(lèi)聲明的嵌套類(lèi)型
1.6.2 可訪問(wèn)性
類(lèi)的每個(gè)成員都有關(guān)聯(lián)的可訪問(wèn)性,它控制能夠訪問(wèn)該成員的程序文本區(qū)域。有5種可能的可訪問(wèn)性形式。表1.7概述了類(lèi)的可訪問(wèn)性的意義。
表1.7 類(lèi)的可訪問(wèn)性
可訪問(wèn)性
意 義
public
訪問(wèn)不受限制
protected
訪問(wèn)僅限于包含類(lèi)或從包含類(lèi)派生的類(lèi)型
internal
訪問(wèn)僅限于當(dāng)前程序集
protected internal
訪問(wèn)僅限于從包含類(lèi)派生的當(dāng)前程序集或類(lèi)型
private
訪問(wèn)僅限于包含類(lèi)
1.6.3 基類(lèi)
類(lèi)的聲明可能通過(guò)在類(lèi)名后加上冒號(hào)和基類(lèi)的名字來(lái)指定一個(gè)基類(lèi)譯注4。省略基類(lèi)等同于直接從object類(lèi)派生。在下面的示例中,Point3D的基類(lèi)是Point,而Point的基類(lèi)是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類(lèi)繼承了其基類(lèi)的成員。繼承意味著類(lèi)將隱式地包含其基類(lèi)的所有成員(除了基類(lèi)的構(gòu)造函數(shù))。派生類(lèi)能夠在繼承基類(lèi)的基礎(chǔ)上增加新的成員,但是它不能移除繼承成員的定義。在前面的示例中,Point3D類(lèi)從Point類(lèi)中繼承了x字段和y字段,并且每一個(gè)Point3D實(shí)例都包含三個(gè)字段x,y和z。
從類(lèi)類(lèi)型到它的任何基類(lèi)類(lèi)型都存在隱式的轉(zhuǎn)換。并且,類(lèi)類(lèi)型的變量能夠引用該類(lèi)的實(shí)例,或者任何派生類(lèi)的實(shí)例。例如,對(duì)于前面給定的類(lèi)聲明,Point類(lèi)型的變量能夠引用Point實(shí)例或者Point3D實(shí)例:
Point a = new Point(10, 20);
Point b = new Point3D(10, 20, 30);
1.6.4 字段
字段是與對(duì)象或類(lèi)相關(guān)聯(lián)的變量。
當(dāng)一個(gè)字段聲明中含有static修飾符時(shí),由該聲明引入的字段為靜態(tài)字段(static field)。它只標(biāo)識(shí)了一個(gè)存儲(chǔ)位置。不管創(chuàng)建了多少個(gè)類(lèi)實(shí)例,靜態(tài)字段都只會(huì)有一個(gè)副本。
當(dāng)一個(gè)字段聲明中不含有static修飾符時(shí),由該聲明引入的字段為實(shí)例字段(instance field)。類(lèi)的每個(gè)實(shí)例都包含了該類(lèi)的所有實(shí)例字段的一個(gè)單獨(dú)副本。
在下面的示例中,Color類(lèi)的每個(gè)實(shí)例都有r,g,b實(shí)例字段的不同副本,但是Black,White,Red,Green和Blue等靜態(tài)字段只有一個(gè)副本:
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;
}
}
如前面的示例所示,通過(guò)readonly修飾符聲明只讀字段。給readonly字段的賦值只能作為聲明的組成部分出現(xiàn),或者在同一類(lèi)中的實(shí)例構(gòu)造函數(shù)或靜態(tài)構(gòu)造函數(shù)中出現(xiàn)。
1.6.5 方法
方法(method)是一種用于實(shí)現(xiàn)可以由對(duì)象或類(lèi)執(zhí)行的計(jì)算或操作的成員。靜態(tài)方法(static method)只能通過(guò)類(lèi)來(lái)訪問(wèn)。實(shí)例方法(instance method)則要通過(guò)類(lèi)的實(shí)例訪問(wèn)。
方法有一個(gè)參數(shù)(parameter)列表(可能為空),表示傳遞給方法的值或者引用;方法還有返回類(lèi)型(return type),用于指定由該方法計(jì)算和返回的值的類(lèi)型。如果方法不返回一個(gè)值,則它的返回類(lèi)型為void。
在聲明方法的類(lèi)中,該方法的簽名必須是惟一的。方法的簽名由它的名稱(chēng)、參數(shù)的數(shù)目、每個(gè)參數(shù)的修飾符和類(lèi)型組成。返回類(lèi)型不是方法簽名的組成部分。
1.6.5.1 參數(shù)
參數(shù)用于將值或者引用變量傳遞給方法。當(dāng)方法被調(diào)用時(shí),方法的參數(shù)譯注5從指定的自變量(argument)譯注6得到它們實(shí)際的值。C#有4種參數(shù):值參數(shù)、引用參數(shù)、輸出參數(shù)和參數(shù)數(shù)組。
值參數(shù)(value parameter)用于輸入?yún)?shù)的傳遞。值參數(shù)相當(dāng)于一個(gè)局部變量,它的初始值是從為該參數(shù)所傳遞的自變量獲得的。對(duì)值參數(shù)的修改不會(huì)影響所傳遞的自變量。
引用參數(shù)(reference parameter)用于輸入和輸出參數(shù)的傳遞。用于引用參數(shù)的自變量必須是一個(gè)變量,并且在方法執(zhí)行期間,引用參數(shù)和作為自變量的變量所表示的是同一個(gè)存儲(chǔ)位置。引用參數(shù)用ref修飾符聲明。下面的示例展示了ref參數(shù)的使用:
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"
}
}
輸出參數(shù)(output parameter)用于輸出參數(shù)的傳遞。輸出參數(shù)類(lèi)似于引用參數(shù),不同之處在于調(diào)用方提供的自變量初始值無(wú)關(guān)緊要。輸出參數(shù)用out修飾符聲明。下面的示例展示了out參數(shù)的使用:
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"
}
}
參數(shù)數(shù)組(parameter array)允許將可變長(zhǎng)度的自變量列表傳遞給方法。參數(shù)數(shù)組用params修飾符聲明。只有方法的最后一個(gè)參數(shù)能夠被聲明為參數(shù)數(shù)組,而且它必須是一維數(shù)組類(lèi)型。System.Console類(lèi)的Write和WriteLine方法是參數(shù)數(shù)組應(yīng)用的很好的例子。它們的聲明形式如下:
public class Console
{
public static void Write(string fmt, params object[] args) {...}
public static void WriteLine(string fmt, params object[] args) {...}
...
}
在方法中使用參數(shù)數(shù)組時(shí),參數(shù)數(shù)組表現(xiàn)得就像常規(guī)的數(shù)組類(lèi)型參數(shù)一樣。然而,帶數(shù)組參數(shù)的方法調(diào)用中,既可以傳遞參數(shù)數(shù)組類(lèi)型的單個(gè)自變量,也可以傳遞參數(shù)數(shù)組的元素類(lèi)型的若干自變量。對(duì)于后者的情形,數(shù)組實(shí)例將自動(dòng)被創(chuàng)建,并且通過(guò)給定的自變量初始化。示例:
Console.WriteLine("x={0} y={1} z={2}", x, y, z);
等價(jià)于下面的語(yǔ)句:
object[] args = new object[3];
args[0] = x;
args[1] = y;
args[2] = z;
Console.WriteLine("x={0} y={1} z={2}", args);
1.6.5.2 方法體和局部變量
方法體指定方法調(diào)用時(shí)所要執(zhí)行的語(yǔ)句。
方法體能夠聲明特定于該方法調(diào)用的變量。這樣的變量被稱(chēng)為局部變量(local variable)。局部變量聲明指定類(lèi)型名、變量名,可能還有初始值。下面的示例聲明了一個(gè)局部變量i,其初始值為0;另一個(gè)局部變量j沒(méi)有初始值。
using System;
class Squares
{
static void Main() {
int i = 0;
int j;
while(i < 10){
j = i * i;
Console.WriteLine("{0} x {0} = {1}", i, j);
i = i + 1;
}
}
}
C#要求局部變量在其值被獲得之前明確賦值(definitely)。例如,假設(shè)前面的變量i的聲明沒(méi)有包含初始值,那么,在接下來(lái)對(duì)i的使用將導(dǎo)致編譯器報(bào)告錯(cuò)誤,原因就是i在程序中沒(méi)有明確賦值。
方法能夠使用return語(yǔ)句將控制返回給它的調(diào)用方。如果方法是void的,則return語(yǔ)句不能指定表達(dá)式;如果方法是非void的,則return語(yǔ)句必須包含表達(dá)式,用于計(jì)算返回值。
1.6.5.3 靜態(tài)方法和實(shí)例方法
若一個(gè)方法聲明中含有static修飾符,則稱(chēng)該方法為靜態(tài)方法(static method)。靜態(tài)方法不對(duì)特定實(shí)例進(jìn)行操作,只能訪問(wèn)靜態(tài)成員。
若一個(gè)方法聲明中沒(méi)有static修飾符,則稱(chēng)該方法為實(shí)例方法(instance method)。實(shí)例方法對(duì)特定實(shí)例進(jìn)行操作,既能夠訪問(wèn)靜態(tài)成員,也能夠訪問(wèn)實(shí)例成員。在調(diào)用實(shí)例方法的實(shí)例上,可以用 this來(lái)訪問(wèn)該實(shí)例,而在靜態(tài)方法中引用this是錯(cuò)誤的。
下面的Entity類(lèi)具有靜態(tài)和實(shí)例兩種成員:
class Entity
{
static int nextSerialNo;
int serialNo;
public Entity() {
serialNo = nextSerialNo++;
}
public int GetSerialNo() {
return serialNo;
}
public static int GetNextSerialNo() {
return nextSerialNo;
}
public static void SetNextSerialNo(int value) {
nextSerialNo = value;
}
}
每一個(gè)Entity實(shí)例包含一個(gè)序列號(hào)(并且假定這里省略了一些其他信息)。Entity構(gòu)造函數(shù)(類(lèi)似于實(shí)例方法)用下一個(gè)有效的序列號(hào)初始化新的實(shí)例。因?yàn)闃?gòu)造函數(shù)是一個(gè)實(shí)例成員,所以,它既可以訪問(wèn)serialNo實(shí)例字段,也可以訪問(wèn)nextSerialNo靜態(tài)字段。
GetNextSerialNo和SetNextSerialNo靜態(tài)方法能夠訪問(wèn)nextSerialNo靜態(tài)字段,但是如果訪問(wèn)serialNo實(shí)例字段就會(huì)產(chǎn)生錯(cuò)誤。
下面的示例展示了Entity類(lèi)的使用:
using System;
class Test
{
static void Main() {
Entity.SetNextSerialNo(1000);
Entity e1 = new Entity();
Entity e2 = new Entity();
Console.WriteLine(e1.GetSerialNo()); //輸出 "1000"
Console.WriteLine(e2.GetSerialNo()); //輸出 "1001"
Console.WriteLine(Entity.GetNextSerialNo()); //輸出 "1002"
}
}
注意,SetNextSerialNo和GetNextSerialNo靜態(tài)方法通過(guò)類(lèi)調(diào)用,而GetSerialNo實(shí)例成員則通過(guò)類(lèi)的實(shí)例調(diào)用。
1.6.5.4 虛擬方法、重寫(xiě)方法和抽象方法
若一個(gè)實(shí)例方法的聲明中含有virtual修飾符,則稱(chēng)該方法為虛擬方法(virtual method)。若其中沒(méi)有virtual修飾符,則稱(chēng)該方法為非虛擬方法(nonvirtual method)。
在一個(gè)虛擬方法調(diào)用中,該調(diào)用所涉及的實(shí)例的運(yùn)行時(shí)類(lèi)型(runtime type)確定了要被調(diào)用的究竟是該方法的哪一個(gè)實(shí)現(xiàn)。在非虛擬方法調(diào)用中,實(shí)例的編譯時(shí)類(lèi)型(compile-time type)是決定性因素。
虛擬方法可以由派生類(lèi)重寫(xiě)(override)譯注7實(shí)現(xiàn)。當(dāng)一個(gè)實(shí)例方法聲明中含有override修飾符時(shí),該方法將重寫(xiě)所繼承的相同簽名的虛擬方法。虛擬方法聲明用于引入新方法,而重寫(xiě)方法聲明則用于使現(xiàn)有的繼承虛擬方法專(zhuān)用化(通過(guò)提供該方法的新實(shí)現(xiàn))。
抽象(abstract)方法是沒(méi)有實(shí)現(xiàn)的虛擬方法。抽象方法的聲明是通過(guò)abstract修飾符實(shí)現(xiàn)的,并且只允許在抽象類(lèi)中使用抽象方法聲明。非抽象類(lèi)的派生類(lèi)需要重寫(xiě)抽象方法。
下面的示例聲明了一個(gè)抽象類(lèi)Expression,它表示一個(gè)表達(dá)式樹(shù)的節(jié)點(diǎn);它有三個(gè)派生類(lèi)Constant,VariableReference,Operation,它們實(shí)現(xiàn)了常數(shù)、變量引用和算術(shù)運(yùn)算的表達(dá)式樹(shù)節(jié)點(diǎn)。
using System;
using System.Collections;
public abstract class Expression
{
public abstract double Evaluate(Hashtable vars);
}
public class Constant: Expression
{
double value;
public Constant(double value) {
this.value = value;
}
public override double Evaluate(Hashtable vars) {
return value;
}
}
public class VariableReference: Expression
{
string name;
public VariableReference(string name) {
this.name = name;
}
public override double Evaluate(Hashtable vars) {
object value = vars[name];
if (value == null) {
throw new Exception("Unknown variable: " + name);
}
return Convert.ToDouble(value);
}
}
public class Operation: Expression
{
Expression left;
char op;
Expression right;
public Operation(Expression left, char op, Expression right) {
this.left = left;
this.op = op;
this.right = right;
}
public override double Evaluate(Hashtable vars) {
double x = left.Evaluate(vars);
double y = right.Evaluate(vars);
switch(op) {
case '+' : return x + y;
case '-' : return x - y;
case '*' : return x * y;
case '/' : return x / y;
}
throw new Exception("Unknown operator");
}
}
前面的4個(gè)類(lèi)用于模型化算術(shù)表達(dá)式。例如,使用這些類(lèi)的實(shí)例,表達(dá)式x+3能夠被表示為如下的形式:
Expression e = new Operation(
new VariableReference("x"),
'+',
new Constant(3));
Expression實(shí)例的Evaluate方法將被調(diào)用,以計(jì)算表達(dá)式的值,從而產(chǎn)生一個(gè)double值。該方法取得一個(gè)包含變量名(輸入的鍵)和值(輸入的值)的Hashtable作為其自變量。Evaluate方法是虛擬的抽象方法,意味著派生類(lèi)必須重寫(xiě)它并提供實(shí)際的實(shí)現(xiàn)。
Evaluate方法的Constant的實(shí)現(xiàn)只是返回保存的常數(shù)。VariableReference的實(shí)現(xiàn)在Hashtable中查找變量名,并且返回相應(yīng)的值。Operation的實(shí)現(xiàn)則首先計(jì)算左操作數(shù)和右操作數(shù)的值(通過(guò)遞歸調(diào)用Evaluate方法),然后執(zhí)行給定的算術(shù)運(yùn)算。
下面的程序使用Expression類(lèi),對(duì)于不同的x和y的值,計(jì)算表達(dá)式x*(y+2)。
using System;
using System.Collections;
class Test
{
static void Main() {
Expression e = new Operation(
new VariableReference("x"),
'*',
new Operation(
new VariableReference("y"),
'+',
new Constant(2)
)
);
Hashtable vars = new Hashtable();
Vars["x"] = 3;
Vars["y"] = 5;
Console.WriteLine(e.Evaluate(vars)); //輸出 "21"
Vars["x"] = 1.5;
Vars["y"] = 9;
Console.WriteLine(e.Evaluate(vars)); //輸出 "16.5"
}
}
1.6.5.5 方法重載
方法重載(Method overloading)允許在同一個(gè)類(lèi)中采用同一個(gè)名稱(chēng)聲明多個(gè)方法,條件是它們的簽名是惟一的。當(dāng)編譯一個(gè)重載方法的調(diào)用時(shí),編譯器采用重載決策(overload resolution)確定應(yīng)調(diào)用的方法。重載決策找到最佳匹配自變量的方法,或者在沒(méi)有找到最佳匹配的方法時(shí)報(bào)告錯(cuò)誤信息。下面的示例展示了重載決策工作機(jī)制。在Main方法中每一個(gè)調(diào)用的注釋說(shuō)明了實(shí)際被調(diào)用的方法。
class Test
{
static void F() {
Console.WriteLine("F()");
}
static void F(object x) {
Console.WriteLine("F(object)");
}
static void F(int x) {
Console.WriteLine("F(int)");
}
static void F(double x) {
Console.WriteLine("F(double)");
}
static void F(double x, dpuble y) {
Console.WriteLine("F(double, double)");
}
static void Main(){
F(); //調(diào)用F()
F(1); //調(diào)用F(int)
F(1.0); //調(diào)用F(double)
F("abc"); //調(diào)用F(object)
F((double)1); //調(diào)用F(double)
F((object)1); //調(diào)用F(object)
F(1, 1); //調(diào)用F(double, double)
}
}
如上例所示,總是通過(guò)自變量到參數(shù)類(lèi)型的顯式的類(lèi)型轉(zhuǎn)換,來(lái)選擇特定方法。
1.6.6 其他函數(shù)成員
類(lèi)的函數(shù)成員(function member)是包含可執(zhí)行語(yǔ)句的成員。前面部分所描述的方法是主要的函數(shù)成員。這一節(jié)討論其他幾種C#支持的函數(shù)成員:構(gòu)造函數(shù)、屬性、索引器、事件、運(yùn)算符、析構(gòu)函數(shù)。
表1.8展示一個(gè)名為L(zhǎng)ist的類(lèi),它實(shí)現(xiàn)一個(gè)可擴(kuò)展的對(duì)象列表。這個(gè)類(lèi)包含了最通用的幾種函數(shù)成員的例子。
表1.8 類(lèi)的函數(shù)成員示例
public class List
{
const int defaultCapacity = 4;
常數(shù)
object[] items;
int count;
字段
(續(xù)表)
public List(): this(defaultCapacity) {}
public List(int capacity) {
items = new object[capacity];
}
構(gòu)造函數(shù)
public int Count {
get { return count; }
}
public string Capacity {
get {
return items.Length;
}
set {
if (value < count) value = count;
if (value != items.Length) {
object[] newItems = new object[value];
Array.Copy(items, 0, newItems, 0, count);
items = newItems;
}
}
}
屬性
public object this[int index] {
get {
return items[index];
}
set {
items[index] = value;
OnListChange();
}
}
索引器
public void Add(object item) {
if (count == Capacity) Capacity = count * 2;
items[count] = item;
count++;
OnChanged();
}
protected virtual void OnChanged() {
if (Changed != null) Changed(this, EventArgs.Empty);
}
public override bool Equals(object other) {
return Equals (this,other as List );
}
static bool Equals ( List a,List b) {
if (a == null) return b == null;
if (b == null || a.count != b.count) return false;
for (int i = 0; i < a.count; i++) {
if (!object.Equals(a.item[i], b.item[i])) {
return false;
}
}
}
方法
public event EventHandler Changed;
事件
public static bool operator ==(List a, List b) {
return Equals(a, b);
}
public static bool operator !=(List a, List b) {
return !Equals(a, b);
}
運(yùn)算符
}
1.6.6.1 構(gòu)造函數(shù)
C#既支持實(shí)例構(gòu)造函數(shù),也支持靜態(tài)構(gòu)造函數(shù)。實(shí)例構(gòu)造函數(shù)(instance constructor)是實(shí)現(xiàn)初始化類(lèi)實(shí)例所需操作的成員。靜態(tài)構(gòu)造函數(shù)(static constructor)是一種在類(lèi)首次加載時(shí)用于實(shí)現(xiàn)初始化類(lèi)本身所需操作的成員。
構(gòu)造函數(shù)的聲明如同方法一樣,不過(guò),它沒(méi)有返回類(lèi)型,它的名字與包含它的類(lèi)名一樣。若構(gòu)造函數(shù)的聲明中包含static修飾符,則它聲明了一個(gè)靜態(tài)構(gòu)造函數(shù),否則聲明實(shí)例構(gòu)造函數(shù)。
實(shí)例構(gòu)造函數(shù)能夠被重載。例如,List聲明了兩個(gè)實(shí)例構(gòu)造函數(shù),一個(gè)不帶參數(shù),一個(gè)帶有一個(gè)int參數(shù)。使用new運(yùn)算符可以調(diào)用實(shí)例參數(shù)。下面的語(yǔ)句使用各個(gè)List類(lèi)的構(gòu)造函數(shù)創(chuàng)建了兩個(gè)List實(shí)例。
List list1 = new List();
List list2 = new List(10);
實(shí)例構(gòu)造函數(shù)不同于其他方法,它是不能被繼承的。并且,一個(gè)類(lèi)除了自己聲明的實(shí)例構(gòu)造函數(shù)外,不可能有其他的實(shí)例構(gòu)造函數(shù)。如果一個(gè)類(lèi)沒(méi)有聲明任何實(shí)例構(gòu)造函數(shù),則會(huì)自動(dòng)地為它提供一個(gè)默認(rèn)的空的實(shí)例構(gòu)造函數(shù)。
1.6.6.2 屬性
屬性(property)是字段的自然擴(kuò)展,兩者都是具有關(guān)聯(lián)類(lèi)型的命名成員,而且訪問(wèn)字段和屬性的語(yǔ)法是相同的。然而,屬性與字段不同,不表示存儲(chǔ)位置。相反,屬性有訪問(wèn)器(accessor),這些訪問(wèn)器指定在它們的值被讀取或?qū)懭霑r(shí)需執(zhí)行的語(yǔ)句。
屬性的聲明類(lèi)似于字段,不同之處在于屬性的聲明以定界符{}之間的get訪問(wèn)器和/或set訪問(wèn)器結(jié)束,而不是分號(hào)。同時(shí)包含get訪問(wèn)器和set訪問(wèn)器的屬性稱(chēng)為讀寫(xiě)屬性(read-write property)。只具有g(shù)et訪問(wèn)器的屬性稱(chēng)為只讀屬性(read-only property)。只具有set訪問(wèn)器的屬性稱(chēng)為只寫(xiě)屬性(write-only property)。
get訪問(wèn)器相當(dāng)于一個(gè)具有屬性類(lèi)型返回值的無(wú)參數(shù)方法。除了作為賦值的目標(biāo)外,當(dāng)在表達(dá)式中引用屬性時(shí),會(huì)調(diào)用該屬性的get訪問(wèn)器以計(jì)算該屬性的值。
set訪問(wèn)器相當(dāng)于一個(gè)具有單個(gè)名為value的參數(shù)和無(wú)返回類(lèi)型的方法。當(dāng)一個(gè)屬性作為賦值的目標(biāo),或者作為++或--運(yùn)算符的操作數(shù)被引用時(shí),就會(huì)調(diào)用set訪問(wèn)器,所傳遞的自變量將提供新值。
List類(lèi)聲明了兩個(gè)屬性Count和Capacity,依次是只讀和只寫(xiě)的。下面是使用這些屬性的示例:
List names = new List();
names.Capacity = 100; //調(diào)用set訪問(wèn)器
int i = names.Count; //調(diào)用get訪問(wèn)器
int j = names.Capacity; //調(diào)用get訪問(wèn)器
與字段和方法類(lèi)似,對(duì)于實(shí)例屬性和靜態(tài)屬性,C#兩者都支持。靜態(tài)屬性是聲明中具有static修飾符,而實(shí)例屬性則沒(méi)有。
屬性的訪問(wèn)器可以是虛擬的。當(dāng)屬性聲明中包含virtual,abstract,override修飾符時(shí),它們將運(yùn)用到屬性訪問(wèn)器。
1.6.6.3 索引器
索引器是這樣一個(gè)成員:它使對(duì)象能夠用與數(shù)組相同的方式進(jìn)行索引。索引器的聲明與屬性很相似,不同之處在于成員的名字是this,后面的參數(shù)列表是在定界符([])之間。參數(shù)在索引器的訪問(wèn)器中是可用的。與屬性類(lèi)似,索引器可以是讀寫(xiě)、只讀、只寫(xiě)的,并且索引器的訪問(wèn)器也可以是虛擬的。
List類(lèi)聲明了單個(gè)讀寫(xiě)索引器,接受一個(gè)int型的參數(shù)。通過(guò)索引器就可能用int值索引List實(shí)例。例如:
List names = new List();
names.Add("Liz");
names.Add("Martha");
names.Add("Beth");
for (int i = 0; i < names.Count; i++) {
string s = (string) names[i];
names[i] = s.ToUpper();
}
索引器能夠被重載,意味著可以聲明多個(gè)索引器,只要它們的參數(shù)個(gè)數(shù)或類(lèi)型不同。
1.6.6.4 事件
事件是使對(duì)象或類(lèi)能夠提供通知的成員。事件的聲明與字段的類(lèi)似,不同之處在于事件聲明包含一個(gè)event關(guān)鍵字,并且事件聲明的類(lèi)型必須是委托類(lèi)型。
在包含事件聲明的類(lèi)中,事件可以像委托類(lèi)型的字段一樣使用(這樣的事件不能是 abstract,而且不能聲明訪問(wèn)器)。該字段保存了一個(gè)委托的引用,表示事件處理程序已經(jīng)被添加到事件上。如果尚未添加任何事件處理程序,則該字段為null。
List類(lèi)聲明了名為Changed的單個(gè)事件成員,Changed事件表明有一個(gè)新項(xiàng)添加到事件處理程序列表,它由OnChanged虛擬方法引發(fā),它首先檢查事件是否為null(意思是沒(méi)有事件處理程序)。引發(fā)事件的通知正好等價(jià)于調(diào)用事件所表示的委托——因此,不需要特殊的語(yǔ)言構(gòu)件引發(fā)事件。
客戶(hù)通過(guò)事件處理程序(event handler)響應(yīng)事件。使用“+=”運(yùn)算符添加或者使用“-=”移除事件處理程序。下面的示例添加一個(gè)事件處理程序到List類(lèi)的Changed事件:
using System;
class Test
{
static int changeCount;
static void ListChanged(object sender, EventArgs e) {
changCount++;
}
static void Main() {
List names = new List();
names.Changed += new EventHandler(ListChanged);
names.Add("Liz");
names.Add("Martha");
names.Add("Beth");
Console.WriteLine(changeCount); //輸出 "3"
}
}
對(duì)于要求控制事件的底層存儲(chǔ)的更高級(jí)場(chǎng)景譯注8,事件的聲明可以顯式地提供add和remove訪問(wèn)器,它們?cè)谀撤N程度上類(lèi)似于屬性的set訪問(wèn)器。
1.6.6.5 運(yùn)算符
運(yùn)算符(operator)是一種函數(shù)成員,用來(lái)定義可應(yīng)用于類(lèi)實(shí)例的特定表達(dá)式運(yùn)算符的含義。有三種運(yùn)算符能夠被定義:一元運(yùn)算符、二元運(yùn)算符和轉(zhuǎn)換運(yùn)算符。所有的運(yùn)算符必須聲明為public和static。
List類(lèi)聲明了兩個(gè)運(yùn)算符,運(yùn)算符 “==”和運(yùn)算符 “!=”,并且向表達(dá)式賦予新的含義,而這些表達(dá)式將這些運(yùn)算符應(yīng)用到List實(shí)例上。特別指出,這些運(yùn)算符定義了兩個(gè)List對(duì)象的相等比較,即使用它們的Equals方法進(jìn)行比較。下面的示例使用“==”運(yùn)算符比較兩個(gè)List實(shí)例。
using System;
class Test
{
static void Main() {
List a = new List();
a.Add(1);
a.Add(2);
List b = new List();
b.Add(1);
b.Add(2);
Console.WriteLine(a == b); //輸出 "True"
b.Add(3);
Console.WriteLine(a == b); //輸出 "False"
}類(lèi)(class)是C#類(lèi)型中最基礎(chǔ)的類(lèi)型。類(lèi)是一個(gè)數(shù)據(jù)結(jié)構(gòu),將狀態(tài)(字段)和行為(方法和其他函數(shù)成員)組合在一個(gè)單元中。類(lèi)提供了用于動(dòng)態(tài)創(chuàng)建類(lèi)實(shí)例的定義,也就是對(duì)象(object)。類(lèi)支持繼承(inheritance)和多態(tài)(polymorphism),即派生類(lèi)能夠擴(kuò)展和特殊化基類(lèi)的機(jī)制。
使用類(lèi)聲明可以創(chuàng)建新的類(lèi)。類(lèi)聲明以一個(gè)聲明頭開(kāi)始,其組成方式如下:先是指定類(lèi)的特性和修飾符,后跟類(lèi)的名字,基類(lèi)(如果有的話)的名字,以及被該類(lèi)實(shí)現(xiàn)的接口名。聲明頭后面就是類(lèi)體了,它由一組包含在大括號(hào)({})中的成員聲明組成。
下面是一個(gè)名為Point的簡(jiǎn)單類(lèi)的聲明:
public class Point
{
public int x, y;
public Point(int x, int y){
this.x = x;
this.y = y;
}
}
使用new運(yùn)算符創(chuàng)建類(lèi)的實(shí)例,它將為新實(shí)例分配內(nèi)存,調(diào)用構(gòu)造函數(shù)初始化實(shí)例,并且返回對(duì)該實(shí)例的引用。下面的語(yǔ)句創(chuàng)建兩個(gè)Point對(duì)象,并且將那些對(duì)象的引用保存到兩個(gè)變量中:
Point p1 = new Point(0, 0);
Point p2 = new Point(10, 20);
當(dāng)不再使用對(duì)象時(shí),該對(duì)象所占的內(nèi)存將被自動(dòng)回收。在C#中,沒(méi)有必要也不可能顯式地釋放對(duì)象。
1.6.1 成員
類(lèi)的成員或者是靜態(tài)成員(static member),或者是實(shí)例成員(instance member)。靜態(tài)成員屬于類(lèi),實(shí)例成員屬于對(duì)象(類(lèi)的實(shí)例)。
表1.6提供了類(lèi)所能包含的各種成員的描述。
表1.6 類(lèi) 的 成 員
成 員
描 述
常數(shù)
與類(lèi)關(guān)聯(lián)的常量值
字段
類(lèi)的變量
方法
能夠被類(lèi)執(zhí)行的計(jì)算和行為
屬性
使對(duì)象能夠讀取和寫(xiě)入類(lèi)的命名屬性
索引器
使對(duì)象能夠用與數(shù)組相同的方式進(jìn)行索引
事件
能夠被類(lèi)產(chǎn)生的通知
運(yùn)算符
類(lèi)支持的轉(zhuǎn)換和表達(dá)式運(yùn)算符
構(gòu)造函數(shù)
初始化類(lèi)的實(shí)例或者類(lèi)本身
析構(gòu)函數(shù)
在永久銷(xiāo)毀類(lèi)的實(shí)例之前執(zhí)行的行為
類(lèi)型
被類(lèi)聲明的嵌套類(lèi)型
1.6.2 可訪問(wèn)性
類(lèi)的每個(gè)成員都有關(guān)聯(lián)的可訪問(wèn)性,它控制能夠訪問(wèn)該成員的程序文本區(qū)域。有5種可能的可訪問(wèn)性形式。表1.7概述了類(lèi)的可訪問(wèn)性的意義。
表1.7 類(lèi)的可訪問(wèn)性
可訪問(wèn)性
意 義
public
訪問(wèn)不受限制
protected
訪問(wèn)僅限于包含類(lèi)或從包含類(lèi)派生的類(lèi)型
internal
訪問(wèn)僅限于當(dāng)前程序集
protected internal
訪問(wèn)僅限于從包含類(lèi)派生的當(dāng)前程序集或類(lèi)型
private
訪問(wèn)僅限于包含類(lèi)
1.6.3 基類(lèi)
類(lèi)的聲明可能通過(guò)在類(lèi)名后加上冒號(hào)和基類(lèi)的名字來(lái)指定一個(gè)基類(lèi)譯注4。省略基類(lèi)等同于直接從object類(lèi)派生。在下面的示例中,Point3D的基類(lèi)是Point,而Point的基類(lèi)是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類(lèi)繼承了其基類(lèi)的成員。繼承意味著類(lèi)將隱式地包含其基類(lèi)的所有成員(除了基類(lèi)的構(gòu)造函數(shù))。派生類(lèi)能夠在繼承基類(lèi)的基礎(chǔ)上增加新的成員,但是它不能移除繼承成員的定義。在前面的示例中,Point3D類(lèi)從Point類(lèi)中繼承了x字段和y字段,并且每一個(gè)Point3D實(shí)例都包含三個(gè)字段x,y和z。
從類(lèi)類(lèi)型到它的任何基類(lèi)類(lèi)型都存在隱式的轉(zhuǎn)換。并且,類(lèi)類(lèi)型的變量能夠引用該類(lèi)的實(shí)例,或者任何派生類(lèi)的實(shí)例。例如,對(duì)于前面給定的類(lèi)聲明,Point類(lèi)型的變量能夠引用Point實(shí)例或者Point3D實(shí)例:
Point a = new Point(10, 20);
Point b = new Point3D(10, 20, 30);
1.6.4 字段
字段是與對(duì)象或類(lèi)相關(guān)聯(lián)的變量。
當(dāng)一個(gè)字段聲明中含有static修飾符時(shí),由該聲明引入的字段為靜態(tài)字段(static field)。它只標(biāo)識(shí)了一個(gè)存儲(chǔ)位置。不管創(chuàng)建了多少個(gè)類(lèi)實(shí)例,靜態(tài)字段都只會(huì)有一個(gè)副本。
當(dāng)一個(gè)字段聲明中不含有static修飾符時(shí),由該聲明引入的字段為實(shí)例字段(instance field)。類(lèi)的每個(gè)實(shí)例都包含了該類(lèi)的所有實(shí)例字段的一個(gè)單獨(dú)副本。
在下面的示例中,Color類(lèi)的每個(gè)實(shí)例都有r,g,b實(shí)例字段的不同副本,但是Black,White,Red,Green和Blue等靜態(tài)字段只有一個(gè)副本:
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;
}
}
如前面的示例所示,通過(guò)readonly修飾符聲明只讀字段。給readonly字段的賦值只能作為聲明的組成部分出現(xiàn),或者在同一類(lèi)中的實(shí)例構(gòu)造函數(shù)或靜態(tài)構(gòu)造函數(shù)中出現(xiàn)。
1.6.5 方法
方法(method)是一種用于實(shí)現(xiàn)可以由對(duì)象或類(lèi)執(zhí)行的計(jì)算或操作的成員。靜態(tài)方法(static method)只能通過(guò)類(lèi)來(lái)訪問(wèn)。實(shí)例方法(instance method)則要通過(guò)類(lèi)的實(shí)例訪問(wèn)。
方法有一個(gè)參數(shù)(parameter)列表(可能為空),表示傳遞給方法的值或者引用;方法還有返回類(lèi)型(return type),用于指定由該方法計(jì)算和返回的值的類(lèi)型。如果方法不返回一個(gè)值,則它的返回類(lèi)型為void。
在聲明方法的類(lèi)中,該方法的簽名必須是惟一的。方法的簽名由它的名稱(chēng)、參數(shù)的數(shù)目、每個(gè)參數(shù)的修飾符和類(lèi)型組成。返回類(lèi)型不是方法簽名的組成部分。
1.6.5.1 參數(shù)
參數(shù)用于將值或者引用變量傳遞給方法。當(dāng)方法被調(diào)用時(shí),方法的參數(shù)譯注5從指定的自變量(argument)譯注6得到它們實(shí)際的值。C#有4種參數(shù):值參數(shù)、引用參數(shù)、輸出參數(shù)和參數(shù)數(shù)組。
值參數(shù)(value parameter)用于輸入?yún)?shù)的傳遞。值參數(shù)相當(dāng)于一個(gè)局部變量,它的初始值是從為該參數(shù)所傳遞的自變量獲得的。對(duì)值參數(shù)的修改不會(huì)影響所傳遞的自變量。
引用參數(shù)(reference parameter)用于輸入和輸出參數(shù)的傳遞。用于引用參數(shù)的自變量必須是一個(gè)變量,并且在方法執(zhí)行期間,引用參數(shù)和作為自變量的變量所表示的是同一個(gè)存儲(chǔ)位置。引用參數(shù)用ref修飾符聲明。下面的示例展示了ref參數(shù)的使用:
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"
}
}
輸出參數(shù)(output parameter)用于輸出參數(shù)的傳遞。輸出參數(shù)類(lèi)似于引用參數(shù),不同之處在于調(diào)用方提供的自變量初始值無(wú)關(guān)緊要。輸出參數(shù)用out修飾符聲明。下面的示例展示了out參數(shù)的使用:
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"
}
}
參數(shù)數(shù)組(parameter array)允許將可變長(zhǎng)度的自變量列表傳遞給方法。參數(shù)數(shù)組用params修飾符聲明。只有方法的最后一個(gè)參數(shù)能夠被聲明為參數(shù)數(shù)組,而且它必須是一維數(shù)組類(lèi)型。System.Console類(lèi)的Write和WriteLine方法是參數(shù)數(shù)組應(yīng)用的很好的例子。它們的聲明形式如下:
public class Console
{
public static void Write(string fmt, params object[] args) {...}
public static void WriteLine(string fmt, params object[] args) {...}
...
}
在方法中使用參數(shù)數(shù)組時(shí),參數(shù)數(shù)組表現(xiàn)得就像常規(guī)的數(shù)組類(lèi)型參數(shù)一樣。然而,帶數(shù)組參數(shù)的方法調(diào)用中,既可以傳遞參數(shù)數(shù)組類(lèi)型的單個(gè)自變量,也可以傳遞參數(shù)數(shù)組的元素類(lèi)型的若干自變量。對(duì)于后者的情形,數(shù)組實(shí)例將自動(dòng)被創(chuàng)建,并且通過(guò)給定的自變量初始化。示例:
Console.WriteLine("x={0} y={1} z={2}", x, y, z);
等價(jià)于下面的語(yǔ)句:
object[] args = new object[3];
args[0] = x;
args[1] = y;
args[2] = z;
Console.WriteLine("x={0} y={1} z={2}", args);
1.6.5.2 方法體和局部變量
方法體指定方法調(diào)用時(shí)所要執(zhí)行的語(yǔ)句。
方法體能夠聲明特定于該方法調(diào)用的變量。這樣的變量被稱(chēng)為局部變量(local variable)。局部變量聲明指定類(lèi)型名、變量名,可能還有初始值。下面的示例聲明了一個(gè)局部變量i,其初始值為0;另一個(gè)局部變量j沒(méi)有初始值。
using System;
class Squares
{
static void Main() {
int i = 0;
int j;
while(i < 10){
j = i * i;
Console.WriteLine("{0} x {0} = {1}", i, j);
i = i + 1;
}
}
}
C#要求局部變量在其值被獲得之前明確賦值(definitely)。例如,假設(shè)前面的變量i的聲明沒(méi)有包含初始值,那么,在接下來(lái)對(duì)i的使用將導(dǎo)致編譯器報(bào)告錯(cuò)誤,原因就是i在程序中沒(méi)有明確賦值。
方法能夠使用return語(yǔ)句將控制返回給它的調(diào)用方。如果方法是void的,則return語(yǔ)句不能指定表達(dá)式;如果方法是非void的,則return語(yǔ)句必須包含表達(dá)式,用于計(jì)算返回值。
1.6.5.3 靜態(tài)方法和實(shí)例方法
若一個(gè)方法聲明中含有static修飾符,則稱(chēng)該方法為靜態(tài)方法(static method)。靜態(tài)方法不對(duì)特定實(shí)例進(jìn)行操作,只能訪問(wèn)靜態(tài)成員。
若一個(gè)方法聲明中沒(méi)有static修飾符,則稱(chēng)該方法為實(shí)例方法(instance method)。實(shí)例方法對(duì)特定實(shí)例進(jìn)行操作,既能夠訪問(wèn)靜態(tài)成員,也能夠訪問(wèn)實(shí)例成員。在調(diào)用實(shí)例方法的實(shí)例上,可以用 this來(lái)訪問(wèn)該實(shí)例,而在靜態(tài)方法中引用this是錯(cuò)誤的。
下面的Entity類(lèi)具有靜態(tài)和實(shí)例兩種成員:
class Entity
{
static int nextSerialNo;
int serialNo;
public Entity() {
serialNo = nextSerialNo++;
}
public int GetSerialNo() {
return serialNo;
}
public static int GetNextSerialNo() {
return nextSerialNo;
}
public static void SetNextSerialNo(int value) {
nextSerialNo = value;
}
}
每一個(gè)Entity實(shí)例包含一個(gè)序列號(hào)(并且假定這里省略了一些其他信息)。Entity構(gòu)造函數(shù)(類(lèi)似于實(shí)例方法)用下一個(gè)有效的序列號(hào)初始化新的實(shí)例。因?yàn)闃?gòu)造函數(shù)是一個(gè)實(shí)例成員,所以,它既可以訪問(wèn)serialNo實(shí)例字段,也可以訪問(wèn)nextSerialNo靜態(tài)字段。
GetNextSerialNo和SetNextSerialNo靜態(tài)方法能夠訪問(wèn)nextSerialNo靜態(tài)字段,但是如果訪問(wèn)serialNo實(shí)例字段就會(huì)產(chǎn)生錯(cuò)誤。
下面的示例展示了Entity類(lèi)的使用:
using System;
class Test
{
static void Main() {
Entity.SetNextSerialNo(1000);
Entity e1 = new Entity();
Entity e2 = new Entity();
Console.WriteLine(e1.GetSerialNo()); //輸出 "1000"
Console.WriteLine(e2.GetSerialNo()); //輸出 "1001"
Console.WriteLine(Entity.GetNextSerialNo()); //輸出 "1002"
}
}
注意,SetNextSerialNo和GetNextSerialNo靜態(tài)方法通過(guò)類(lèi)調(diào)用,而GetSerialNo實(shí)例成員則通過(guò)類(lèi)的實(shí)例調(diào)用。
1.6.5.4 虛擬方法、重寫(xiě)方法和抽象方法
若一個(gè)實(shí)例方法的聲明中含有virtual修飾符,則稱(chēng)該方法為虛擬方法(virtual method)。若其中沒(méi)有virtual修飾符,則稱(chēng)該方法為非虛擬方法(nonvirtual method)。
在一個(gè)虛擬方法調(diào)用中,該調(diào)用所涉及的實(shí)例的運(yùn)行時(shí)類(lèi)型(runtime type)確定了要被調(diào)用的究竟是該方法的哪一個(gè)實(shí)現(xiàn)。在非虛擬方法調(diào)用中,實(shí)例的編譯時(shí)類(lèi)型(compile-time type)是決定性因素。
虛擬方法可以由派生類(lèi)重寫(xiě)(override)譯注7實(shí)現(xiàn)。當(dāng)一個(gè)實(shí)例方法聲明中含有override修飾符時(shí),該方法將重寫(xiě)所繼承的相同簽名的虛擬方法。虛擬方法聲明用于引入新方法,而重寫(xiě)方法聲明則用于使現(xiàn)有的繼承虛擬方法專(zhuān)用化(通過(guò)提供該方法的新實(shí)現(xiàn))。
抽象(abstract)方法是沒(méi)有實(shí)現(xiàn)的虛擬方法。抽象方法的聲明是通過(guò)abstract修飾符實(shí)現(xiàn)的,并且只允許在抽象類(lèi)中使用抽象方法聲明。非抽象類(lèi)的派生類(lèi)需要重寫(xiě)抽象方法。
下面的示例聲明了一個(gè)抽象類(lèi)Expression,它表示一個(gè)表達(dá)式樹(shù)的節(jié)點(diǎn);它有三個(gè)派生類(lèi)Constant,VariableReference,Operation,它們實(shí)現(xiàn)了常數(shù)、變量引用和算術(shù)運(yùn)算的表達(dá)式樹(shù)節(jié)點(diǎn)。
using System;
using System.Collections;
public abstract class Expression
{
public abstract double Evaluate(Hashtable vars);
}
public class Constant: Expression
{
double value;
public Constant(double value) {
this.value = value;
}
public override double Evaluate(Hashtable vars) {
return value;
}
}
public class VariableReference: Expression
{
string name;
public VariableReference(string name) {
this.name = name;
}
public override double Evaluate(Hashtable vars) {
object value = vars[name];
if (value == null) {
throw new Exception("Unknown variable: " + name);
}
return Convert.ToDouble(value);
}
}
public class Operation: Expression
{
Expression left;
char op;
Expression right;
public Operation(Expression left, char op, Expression right) {
this.left = left;
this.op = op;
this.right = right;
}
public override double Evaluate(Hashtable vars) {
double x = left.Evaluate(vars);
double y = right.Evaluate(vars);
switch(op) {
case '+' : return x + y;
case '-' : return x - y;
case '*' : return x * y;
case '/' : return x / y;
}
throw new Exception("Unknown operator");
}
}
前面的4個(gè)類(lèi)用于模型化算術(shù)表達(dá)式。例如,使用這些類(lèi)的實(shí)例,表達(dá)式x+3能夠被表示為如下的形式:
Expression e = new Operation(
new VariableReference("x"),
'+',
new Constant(3));
Expression實(shí)例的Evaluate方法將被調(diào)用,以計(jì)算表達(dá)式的值,從而產(chǎn)生一個(gè)double值。該方法取得一個(gè)包含變量名(輸入的鍵)和值(輸入的值)的Hashtable作為其自變量。Evaluate方法是虛擬的抽象方法,意味著派生類(lèi)必須重寫(xiě)它并提供實(shí)際的實(shí)現(xiàn)。
Evaluate方法的Constant的實(shí)現(xiàn)只是返回保存的常數(shù)。VariableReference的實(shí)現(xiàn)在Hashtable中查找變量名,并且返回相應(yīng)的值。Operation的實(shí)現(xiàn)則首先計(jì)算左操作數(shù)和右操作數(shù)的值(通過(guò)遞歸調(diào)用Evaluate方法),然后執(zhí)行給定的算術(shù)運(yùn)算。
下面的程序使用Expression類(lèi),對(duì)于不同的x和y的值,計(jì)算表達(dá)式x*(y+2)。
using System;
using System.Collections;
class Test
{
static void Main() {
Expression e = new Operation(
new VariableReference("x"),
'*',
new Operation(
new VariableReference("y"),
'+',
new Constant(2)
)
);
Hashtable vars = new Hashtable();
Vars["x"] = 3;
Vars["y"] = 5;
Console.WriteLine(e.Evaluate(vars)); //輸出 "21"
Vars["x"] = 1.5;
Vars["y"] = 9;
Console.WriteLine(e.Evaluate(vars)); //輸出 "16.5"
}
}
1.6.5.5 方法重載
方法重載(Method overloading)允許在同一個(gè)類(lèi)中采用同一個(gè)名稱(chēng)聲明多個(gè)方法,條件是它們的簽名是惟一的。當(dāng)編譯一個(gè)重載方法的調(diào)用時(shí),編譯器采用重載決策(overload resolution)確定應(yīng)調(diào)用的方法。重載決策找到最佳匹配自變量的方法,或者在沒(méi)有找到最佳匹配的方法時(shí)報(bào)告錯(cuò)誤信息。下面的示例展示了重載決策工作機(jī)制。在Main方法中每一個(gè)調(diào)用的注釋說(shuō)明了實(shí)際被調(diào)用的方法。
class Test
{
static void F() {
Console.WriteLine("F()");
}
static void F(object x) {
Console.WriteLine("F(object)");
}
static void F(int x) {
Console.WriteLine("F(int)");
}
static void F(double x) {
Console.WriteLine("F(double)");
}
static void F(double x, dpuble y) {
Console.WriteLine("F(double, double)");
}
static void Main(){
F(); //調(diào)用F()
F(1); //調(diào)用F(int)
F(1.0); //調(diào)用F(double)
F("abc"); //調(diào)用F(object)
F((double)1); //調(diào)用F(double)
F((object)1); //調(diào)用F(object)
F(1, 1); //調(diào)用F(double, double)
}
}
如上例所示,總是通過(guò)自變量到參數(shù)類(lèi)型的顯式的類(lèi)型轉(zhuǎn)換,來(lái)選擇特定方法。
1.6.6 其他函數(shù)成員
類(lèi)的函數(shù)成員(function member)是包含可執(zhí)行語(yǔ)句的成員。前面部分所描述的方法是主要的函數(shù)成員。這一節(jié)討論其他幾種C#支持的函數(shù)成員:構(gòu)造函數(shù)、屬性、索引器、事件、運(yùn)算符、析構(gòu)函數(shù)。
表1.8展示一個(gè)名為L(zhǎng)ist的類(lèi),它實(shí)現(xiàn)一個(gè)可擴(kuò)展的對(duì)象列表。這個(gè)類(lèi)包含了最通用的幾種函數(shù)成員的例子。
表1.8 類(lèi)的函數(shù)成員示例
public class List
{
const int defaultCapacity = 4;
常數(shù)
object[] items;
int count;
字段
(續(xù)表)
public List(): this(defaultCapacity) {}
public List(int capacity) {
items = new object[capacity];
}
構(gòu)造函數(shù)
public int Count {
get { return count; }
}
public string Capacity {
get {
return items.Length;
}
set {
if (value < count) value = count;
if (value != items.Length) {
object[] newItems = new object[value];
Array.Copy(items, 0, newItems, 0, count);
items = newItems;
}
}
}
屬性
public object this[int index] {
get {
return items[index];
}
set {
items[index] = value;
OnListChange();
}
}
索引器
public void Add(object item) {
if (count == Capacity) Capacity = count * 2;
items[count] = item;
count++;
OnChanged();
}
protected virtual void OnChanged() {
if (Changed != null) Changed(this, EventArgs.Empty);
}
public override bool Equals(object other) {
return Equals (this,other as List );
}
static bool Equals ( List a,List b) {
if (a == null) return b == null;
if (b == null || a.count != b.count) return false;
for (int i = 0; i < a.count; i++) {
if (!object.Equals(a.item[i], b.item[i])) {
return false;
}
}
}
方法
public event EventHandler Changed;
事件
public static bool operator ==(List a, List b) {
return Equals(a, b);
}
public static bool operator !=(List a, List b) {
return !Equals(a, b);
}
運(yùn)算符
}
1.6.6.1 構(gòu)造函數(shù)
C#既支持實(shí)例構(gòu)造函數(shù),也支持靜態(tài)構(gòu)造函數(shù)。實(shí)例構(gòu)造函數(shù)(instance constructor)是實(shí)現(xiàn)初始化類(lèi)實(shí)例所需操作的成員。靜態(tài)構(gòu)造函數(shù)(static constructor)是一種在類(lèi)首次加載時(shí)用于實(shí)現(xiàn)初始化類(lèi)本身所需操作的成員。
構(gòu)造函數(shù)的聲明如同方法一樣,不過(guò),它沒(méi)有返回類(lèi)型,它的名字與包含它的類(lèi)名一樣。若構(gòu)造函數(shù)的聲明中包含static修飾符,則它聲明了一個(gè)靜態(tài)構(gòu)造函數(shù),否則聲明實(shí)例構(gòu)造函數(shù)。
實(shí)例構(gòu)造函數(shù)能夠被重載。例如,List聲明了兩個(gè)實(shí)例構(gòu)造函數(shù),一個(gè)不帶參數(shù),一個(gè)帶有一個(gè)int參數(shù)。使用new運(yùn)算符可以調(diào)用實(shí)例參數(shù)。下面的語(yǔ)句使用各個(gè)List類(lèi)的構(gòu)造函數(shù)創(chuàng)建了兩個(gè)List實(shí)例。
List list1 = new List();
List list2 = new List(10);
實(shí)例構(gòu)造函數(shù)不同于其他方法,它是不能被繼承的。并且,一個(gè)類(lèi)除了自己聲明的實(shí)例構(gòu)造函數(shù)外,不可能有其他的實(shí)例構(gòu)造函數(shù)。如果一個(gè)類(lèi)沒(méi)有聲明任何實(shí)例構(gòu)造函數(shù),則會(huì)自動(dòng)地為它提供一個(gè)默認(rèn)的空的實(shí)例構(gòu)造函數(shù)。
1.6.6.2 屬性
屬性(property)是字段的自然擴(kuò)展,兩者都是具有關(guān)聯(lián)類(lèi)型的命名成員,而且訪問(wèn)字段和屬性的語(yǔ)法是相同的。然而,屬性與字段不同,不表示存儲(chǔ)位置。相反,屬性有訪問(wèn)器(accessor),這些訪問(wèn)器指定在它們的值被讀取或?qū)懭霑r(shí)需執(zhí)行的語(yǔ)句。
屬性的聲明類(lèi)似于字段,不同之處在于屬性的聲明以定界符{}之間的get訪問(wèn)器和/或set訪問(wèn)器結(jié)束,而不是分號(hào)。同時(shí)包含get訪問(wèn)器和set訪問(wèn)器的屬性稱(chēng)為讀寫(xiě)屬性(read-write property)。只具有g(shù)et訪問(wèn)器的屬性稱(chēng)為只讀屬性(read-only property)。只具有set訪問(wèn)器的屬性稱(chēng)為只寫(xiě)屬性(write-only property)。
get訪問(wèn)器相當(dāng)于一個(gè)具有屬性類(lèi)型返回值的無(wú)參數(shù)方法。除了作為賦值的目標(biāo)外,當(dāng)在表達(dá)式中引用屬性時(shí),會(huì)調(diào)用該屬性的get訪問(wèn)器以計(jì)算該屬性的值。
set訪問(wèn)器相當(dāng)于一個(gè)具有單個(gè)名為value的參數(shù)和無(wú)返回類(lèi)型的方法。當(dāng)一個(gè)屬性作為賦值的目標(biāo),或者作為++或--運(yùn)算符的操作數(shù)被引用時(shí),就會(huì)調(diào)用set訪問(wèn)器,所傳遞的自變量將提供新值。
List類(lèi)聲明了兩個(gè)屬性Count和Capacity,依次是只讀和只寫(xiě)的。下面是使用這些屬性的示例:
List names = new List();
names.Capacity = 100; //調(diào)用set訪問(wèn)器
int i = names.Count; //調(diào)用get訪問(wèn)器
int j = names.Capacity; //調(diào)用get訪問(wèn)器
與字段和方法類(lèi)似,對(duì)于實(shí)例屬性和靜態(tài)屬性,C#兩者都支持。靜態(tài)屬性是聲明中具有static修飾符,而實(shí)例屬性則沒(méi)有。
屬性的訪問(wèn)器可以是虛擬的。當(dāng)屬性聲明中包含virtual,abstract,override修飾符時(shí),它們將運(yùn)用到屬性訪問(wèn)器。
1.6.6.3 索引器
索引器是這樣一個(gè)成員:它使對(duì)象能夠用與數(shù)組相同的方式進(jìn)行索引。索引器的聲明與屬性很相似,不同之處在于成員的名字是this,后面的參數(shù)列表是在定界符([])之間。參數(shù)在索引器的訪問(wèn)器中是可用的。與屬性類(lèi)似,索引器可以是讀寫(xiě)、只讀、只寫(xiě)的,并且索引器的訪問(wèn)器也可以是虛擬的。
List類(lèi)聲明了單個(gè)讀寫(xiě)索引器,接受一個(gè)int型的參數(shù)。通過(guò)索引器就可能用int值索引List實(shí)例。例如:
List names = new List();
names.Add("Liz");
names.Add("Martha");
names.Add("Beth");
for (int i = 0; i < names.Count; i++) {
string s = (string) names[i];
names[i] = s.ToUpper();
}
索引器能夠被重載,意味著可以聲明多個(gè)索引器,只要它們的參數(shù)個(gè)數(shù)或類(lèi)型不同。
1.6.6.4 事件
事件是使對(duì)象或類(lèi)能夠提供通知的成員。事件的聲明與字段的類(lèi)似,不同之處在于事件聲明包含一個(gè)event關(guān)鍵字,并且事件聲明的類(lèi)型必須是委托類(lèi)型。
在包含事件聲明的類(lèi)中,事件可以像委托類(lèi)型的字段一樣使用(這樣的事件不能是 abstract,而且不能聲明訪問(wèn)器)。該字段保存了一個(gè)委托的引用,表示事件處理程序已經(jīng)被添加到事件上。如果尚未添加任何事件處理程序,則該字段為null。
List類(lèi)聲明了名為Changed的單個(gè)事件成員,Changed事件表明有一個(gè)新項(xiàng)添加到事件處理程序列表,它由OnChanged虛擬方法引發(fā),它首先檢查事件是否為null(意思是沒(méi)有事件處理程序)。引發(fā)事件的通知正好等價(jià)于調(diào)用事件所表示的委托——因此,不需要特殊的語(yǔ)言構(gòu)件引發(fā)事件。
客戶(hù)通過(guò)事件處理程序(event handler)響應(yīng)事件。使用“+=”運(yùn)算符添加或者使用“-=”移除事件處理程序。下面的示例添加一個(gè)事件處理程序到List類(lèi)的Changed事件:
using System;
class Test
{
static int changeCount;
static void ListChanged(object sender, EventArgs e) {
changCount++;
}
static void Main() {
List names = new List();
names.Changed += new EventHandler(ListChanged);
names.Add("Liz");
names.Add("Martha");
names.Add("Beth");
Console.WriteLine(changeCount); //輸出 "3"
}
}
對(duì)于要求控制事件的底層存儲(chǔ)的更高級(jí)場(chǎng)景譯注8,事件的聲明可以顯式地提供add和remove訪問(wèn)器,它們?cè)谀撤N程度上類(lèi)似于屬性的set訪問(wèn)器。
1.6.6.5 運(yùn)算符
運(yùn)算符(operator)是一種函數(shù)成員,用來(lái)定義可應(yīng)用于類(lèi)實(shí)例的特定表達(dá)式運(yùn)算符的含義。有三種運(yùn)算符能夠被定義:一元運(yùn)算符、二元運(yùn)算符和轉(zhuǎn)換運(yùn)算符。所有的運(yùn)算符必須聲明為public和static。
List類(lèi)聲明了兩個(gè)運(yùn)算符,運(yùn)算符 “==”和運(yùn)算符 “!=”,并且向表達(dá)式賦予新的含義,而這些表達(dá)式將這些運(yùn)算符應(yīng)用到List實(shí)例上。特別指出,這些運(yùn)算符定義了兩個(gè)List對(duì)象的相等比較,即使用它們的Equals方法進(jìn)行比較。下面的示例使用“==”運(yùn)算符比較兩個(gè)List實(shí)例。
using System;
class Test
{
static void Main() {
List a = new List();
a.Add(1);
a.Add(2);
List b = new List();
b.Add(1);
b.Add(2);
Console.WriteLine(a == b); //輸出 "True"
b.Add(3);
Console.WriteLine(a == b); //輸出 "False"
}
}
第一個(gè)Console.WriteLine輸出True,原因是兩個(gè)List集合對(duì)象包含個(gè)數(shù)和值都相同的對(duì)象。假如List沒(méi)有定義運(yùn)算符 “==”,那么第一個(gè)Console.WriteLine將輸出False,因?yàn)閍和b引用不同的List實(shí)例。
1.6.6.6 析構(gòu)函數(shù)
析構(gòu)函數(shù)(destructor)是用于實(shí)現(xiàn)析構(gòu)類(lèi)實(shí)例所需操作的成員。析構(gòu)函數(shù)不能帶參數(shù),不能具有可訪問(wèn)性修飾符,也不能被顯式地調(diào)用。垃圾回收期間會(huì)自動(dòng)調(diào)用所涉及實(shí)例的析構(gòu)函數(shù)。
垃圾回收器在決定何時(shí)回收對(duì)象和運(yùn)行析構(gòu)函數(shù)方面采取寬松的策略。特別指出,析構(gòu)函數(shù)的調(diào)用時(shí)機(jī)是不確定的,并且析構(gòu)函數(shù)可能運(yùn)行在任何線程上。由于這些或者其他原因,只有沒(méi)有其他可行的解決方案,類(lèi)才實(shí)現(xiàn)析構(gòu)函數(shù)。
}
第一個(gè)Console.WriteLine輸出True,原因是兩個(gè)List集合對(duì)象包含個(gè)數(shù)和值都相同的對(duì)象。假如List沒(méi)有定義運(yùn)算符 “==”,那么第一個(gè)Console.WriteLine將輸出False,因?yàn)閍和b引用不同的List實(shí)例。
1.6.6.6 析構(gòu)函數(shù)
析構(gòu)函數(shù)(destructor)是用于實(shí)現(xiàn)析構(gòu)類(lèi)實(shí)例所需操作的成員。析構(gòu)函數(shù)不能帶參數(shù),不能具有可訪問(wèn)性修飾符,也不能被顯式地調(diào)用。垃圾回收期間會(huì)自動(dòng)調(diào)用所涉及實(shí)例的析構(gòu)函數(shù)。
垃圾回收器在決定何時(shí)回收對(duì)象和運(yùn)行析構(gòu)函數(shù)方面采取寬松的策略。特別指出,析構(gòu)函數(shù)的調(diào)用時(shí)機(jī)是不確定的,并且析構(gòu)函數(shù)可能運(yùn)行在任何線程上。由于這些或者其他原因,只有沒(méi)有其他可行的解決方案,類(lèi)才實(shí)現(xiàn)析構(gòu)函數(shù)。
更多信息請(qǐng)查看IT技術(shù)專(zhuān)欄