瓦茨S.汉弗莱曾经说过"软件开发的历史就是软件规模逐渐变大的历史",在软硬件环境逐渐复杂的情况下,软件如何得到良好的维护?面向对象程序设计在某种程度上通过强调可重复性解决了这一问题。20世纪70年代的Smalltalk语言在面向对象方面堪称经典------以至于40年后的今天,我们依然将这一语言视为面向对象语言的基础。
传统的程序设计将程序看做一系列函数的集合,而面向对象程序设计中的基本单元为对象,每一个对象都可以接收数据、处理数据并将数据传送给其他对象,每个对象都是责任和数据的结合体。
C#就是一种面向对象的语言,其他还包括大家熟悉的Java、C++等。面向对象的语言具有一些基础理论,或者叫做基本特征,它们是类、对象、方法、消息传递机制、继承性、封装性、多态性、抽象性。
本课将要学习其中最核心的概念之一:类。学习一门语言时,熟悉语法是基本的要求,更进一步则是学习其中的编程思想,C#语言本身也是语言开发者思想的载体,或者是一种思想表达。我们在本课中也会适时为读者补充一些相关的知识介绍,如果要更深入地学习,望大家能够自行阅读面向对象编程方面的相关书籍,以更好地使用C#这个工具。
阅读本系列课程就像漫步在知识的沙滩上,希望大家偶尔能够捡到几只漂亮的贝壳。
9.1类是什么
我们以ATM取款机软件为例来说明类的稳定性。一开始,用户希望ATM支持本行银行卡,然后又想支持银联银行卡,再往后可能又想支持跨行、跨国账户结转,等等。这说明软件的功能最易发生变化,数据次之。我们将功能和数据分别比做鸡蛋的蛋清和蛋黄,它们本身是不稳定的,如果没有一个容器,你很难将它们一起拿走,但是如果将它们放到一个蛋壳里面,作为一个完整的鸡蛋的话,整体就是稳定的,如图9-1所示。

这个整体就是一个对象,数据就是对象的属性、字段,而功能就是对象的函数成员,它可以提供数据运算。这个对象映射到计算机语言中,就是类。还拿鸡蛋做例子,鸡蛋是类,但是鸡蛋在这里只是一个概念,它表示所有鸡蛋,如果我们要吃鸡蛋,或者拿鸡蛋炒西红柿的话,这时都是某个具体鸡蛋,这些鸡蛋就是类的实例,或者说类具体化了。
可以说,类描述了它所代表对象的共同属性和行为。因此,一个类最基本的两个组成部分是数据和行为(功能)。
其中,数据包含字段(Field)和属性(Property),用于保存与该类有关的信息变量;而行为则是功能、动作,表示类所能提供的服务。
9.2 "Hello World!"程序回顾
我们再来回顾一下第3课中的Hello World程序示例,如代码清单9-1
所示。
代码清单9-1 Hello World程序示例
cs
using System;
namespace ProgrammingCSharp4
{
class HelloWorldClass
{
static void Main()
{
Console.WriteLine("Hello World!");
}
}
}
这段代码共涉及两个类:
❑HelloWorldClass:它是我们自定义的类,没有属性成员,但包括一个静态成员方法------Main()。其实,除了Main方法,它还从基类(Object)继承了四个方法:Equals、GetHashCode、GetType、ToString。关于继承我们将在第11课进行详述。
❑Console:控制台类,这里用到的是将字符串写入控制台的WriteLine方法。
如图9-2所示。

9.3 类的声明
声明一个类非常简单,C#中使用class关键字来定义类,如代码清单9-2所示。
代码清单9-2 类的声明
cs
class ClassExample
{
//类成员定义
}
这段代码定义了一个类ClassExample,此时就可以在项目中允许访问该类的地方对类进行实例化了,大家注意到,class关键字前没有使用任何访问修饰符(后面会讲到),此时默认为internal修饰符,即仅在同一个程序集内可访问,也可以显式地使用internal修饰符,见代码清单9-3。
代码清单9-3 显式使用internal修饰符
cs
internal class ClassExample
{
//类成员定义
}
如果想要将ClassExample类声明为公共类,可以使用public修饰符,更多的修饰符我们将在9.9节进行介绍,大家也可以自行参阅。
9.4 类的成员
从级别来分,类的成员包括静态成员和实例成员。静态成员是类级别,不属于类的实例,而实例成员则属于类的实例(对象)。
从功能来分,类的成员包括字段、属性、方法、索引器、构造函数等,其中字段属于数据成员,方法属于函数成员。
表9-1是类所能包含的各种成员。

9.4.1 字段
字段用于存储类所需要的数据。例如,一个类(汽车)可能有三个字段:速度、方向、行驶里程数(当然还可能有很多其他属性,我们此处只是举例说明,尽量将它简化而已)。如图9-3所示。

声明一个字段时,只需要说明如下3个要素即可:
❑访问级别
❑字段的类型
❑字段名称
下面我们提供一个示例供读者参考,如代码清单9-4所示。
代码清单9-4 字段
cs
class Car
{
public double speed;
protected string direction;
private double distance;
}
可以使用点运算符访问对象中的字段,如下:
对象[1]名.字段名
现在,我们通过代码清单9-5进行示例说明。
代码清单9-5 为字段赋值
cs
using System;
class ClassExample
{
public static void Main()
{
Car car = new Car();
// 为字段赋值
car.speed = 10f;
// 获取字段的值
Console.WriteLine("汽车的速度为:{0}", car.speed);
}
}
读者可能已经注意到了,Car类有三个字段:speed、direction、distance,这里仅仅对speed进行了赋值和读取操作,那么能否也对另外两个字段进行同样的操作呢?读者可以自行试验,答案是否定的。原因如下:
❑direction字段的访问级别为protected,属于保护级别,只有使用该类作为基类的派生类型才能访问;
❑distance字段的访问级别为private,属于私有级别,只有声明它的类或结构才能访问。
关于访问级别的更多内容,请参考9.9节。注意,这里讲的字段均为实例字段,和实例字段相对的是静态字段,如下面内容所述。
9.4.2 静态字段
和前述的实例字段不同的是,静态字段是类级别的,就是说访问它不需要先实例化类,直接使用"类名.静态字段名"即可访问。先来看一个静态字段的例子,如代码清单9-6所示。
代码清单9-6 静态字段声明
cs
//汽车型号
public static string Type;
我们看到,和9.10节讲的静态方法类似,在实例字段声明的基础上添加static修饰符即可,static关键字要位于字段类型之前。下面看一个示例,如代码清单9-7所示。
代码清单9-7 静态字段的声明及访问
cs
using System;
namespace ProgrammingCSharp4
{
// 示例类
class ClassExample
{
public static void Main()
{
// 实例化汽车对象
Car car = new Car();
// 为实例字段赋值
car.speed = 10f;
// 静态字段只能通过类名访问
Car.Type = "BENZ";
// 错误!通过类实例无法访问静态成员
// car.Type = "CHERY";
// 输出汽车速度
Console.WriteLine("汽车的速度为:{0}", car.speed);
}
}
// 汽车类
class Car
{
// 当前行驶速度(实例字段)
public double speed;
// 当前行驶方向(受保护字段)
protected string direction;
// 已行驶距离(私有字段)
private double distance;
// 汽车型号(静态字段)
public static string Type;
// 构造函数
public Car()
{
// 初始化静态字段默认值
Type = "BMW";
}
}
}
上述代码中,Type字段为静态字段,意味着Car类的所有实例的Type字段的值都相同,而speed等实例字段则各不相同。
静态字段的特点如下:
❑它不属于特定对象,而属于某一个类;
❑它不用创建类的实例即可访问(使用点运算符,且满足相应访问级别的情况下)。
接下来,我们对实例字段和静态字段做个对比,以加深对静态字段的理解。如表9-2所示。

9.4.3 方法
"方法"是类的成员之一,第9课有详细介绍,此处不再赘述。
9.4.4 字段的初始化
所有的字段级变量被编译器初始化为所属类型中等价于0的值。如布尔型被初始化为false,数值型被初始化为0或者0.0,所有的引用类型都被初始化为null。各种数据类型的默认值如表9-3所示。

当然,也可以在声明时就立即进行初始化,而且我们推荐这种方式,这是一个好的编程习惯。下面我们通过示例来说明,如代码清单9-8所示。
代码清单9-8 字段的初始化
cs
using System;
namespace ProgrammingCSharp4
{
class ClassExample
{
public static void Main()
{
Car car = new Car();
// 获取字段speed的默认值(默认0)
Console.WriteLine("汽车的速度为:{0}", car.speed);
// 获取字段distance的默认值(默认0)
Console.WriteLine("汽车的行驶距离为:{0}", car.distance);
// 获取字段isOutOfWarranty的默认值(默认False)
Console.WriteLine("汽车是否过保修期:{0}", car.isOutOfWarranty);
// 声明时已初始化,输出初始化值
Console.WriteLine("汽车的品牌为:{0}", car.type);
}
}
class Car
{
// 当前行驶速度(double类型默认值:0)
public double speed;
// 当前行驶距离(uint类型默认值:0)
public uint distance;
// 是否已过保修期(bool类型默认值:false)
public bool isOutOfWarranty;
// 车辆品牌(声明时显式初始化:BMW)
public string type = "BMW";
}
}
上述代码输出为:
汽车的速度为:0
汽车的行驶距离为:0
汽车是否过保修期:False
9.4.5 属性
C#属性是字段的扩展,它配合C#中的字段使用,用以构造一个安全的应用程序。属性提供了灵活的机制来读取、编写或计算私有字段的值,可以像使用公共数据成员一样使用属性,但实际上它们是称做"访问器"的特殊方法,其设计目的主要是为了实现面向对象(ObjectOriented,OO)中的封装思想。根据该思想,字段最好设为private,一个设计完善的类最好不要直接把字段声明为公有或受保护的,以阻止客户端直接进行访问,其中一个主要原因是,客户端直接对公有字段进行读写,使得我们无法对字段的访问进行灵活的控制,比如控制字段只读或者只写将很难实现。
下面,我们将进一步学习属性及访问器。
1.属性声明和访问器
属性的声明主要包含以下几个部分:访问修饰符、属性类型、属性名称、访问器。
先来看一个属性声明的例子,如代码清单9-9所示。
代码清单9-9 属性声明

属性访问器包括get访问器和set访问器,分别用于字段的读写操作,但要注意的是,属性本身并不一定和字段相联系。仅包含get访问器的属性为只读属性,仅包含set访问器的属性为只写属性,同时包含两种访问器的属性可读也可写,称做读写属性。代码清单9-9中声明的属性就是一个读写属性。
get访问器的责任是返回字段的值,字段就是该属性所封装的字段,那么很自然地,返回值的类型应该和字段的类型一致。代码清单9-9中的get访问器返回的就是name字段的值,且类型和name字段类型相同,都是string类型。
set访问器的责任是为字段赋值,怎么赋值呢?它是通过一个隐式的参数value来实现值的传入,在代码清单9-9中的set访问器中,name的值就是通过value这个隐式参数赋予的。
注意 在属性中,除了get和set访问器,不允许有其他方法出现。
2.属性和关联字段
代码清单9-10 中第4行的speed字段就是属性的关联字段。在一个属性中,get访问器和set访问器的职责之一就是对关联字段的封装。其语法为:
❑声明一个私有的字段级变量(这里是类字段,和局部变量不同,请注意区分);
❑使用下列语法声明一个属性,将私有字段封装起来:
public[数据类型][属性名]
{
get
{
//返回字段值
}
set
{
//使用隐式参数value为字段赋值
}
}
我们先来看一个示例,如代码清单9-10所示。
代码清单9-10 属性声明和访问器
cs
class Car
{
// 当前行驶速度(私有字段)
private double speed;
// 属性(封装私有字段speed)
public double Speed
{
// 访问器 - 获取字段值
get
{
// 返回私有字段speed的值
return speed;
}
// 访问器 - 设置字段值
set
{
// 为私有字段speed赋值(value为传入的参数值)
speed = value;
}
}
}
我们对上述代码进行简要的讲解,如表9-4所示。

注意 从内存分配的角度来看私有字段的声明和初始化,CLR为其分配了内存,而并未给属性分配内存,因为属性本身并不存储数据,它操作的是关联字段。
3.自动实现的属性
自C#3.0以来,我们可以使用另外一种更加简洁的语法来定义属性,其语法如下:
public[数据类型][属性名]
{
get;
set;
}
可见,上述代码可使属性声明变得更加简洁。不过这种语法形式也是有限制的,就是仅当属性访问器中不需要其他的逻辑时,才可以使用这种语法形式。如果属性的访问器中需要执行某些计算,就还是需要使用关联字段的方式。本质上,自动实现的属性这种语法也有自己的关联字段,只不过这个关联字段也是隐式的,是编译器自动生成的。由此可见,编译器帮助我们做了很多的工作,减少了我们的工作量。
下面,我们将从本质上对自动实现的属性进行剖析,了解一下编译器究竟为我们做了哪些工作,并且是如何做的,这对于深刻理解C#的工作原理是大有裨益的。通过查看生成后的CIL和原C#代码来一探究竟,可能是一个不错的主意。你可以在"开始"菜单中"Visual Studio 2010"目录的"Microsoft Windows SDK Tools"下找到"IL反汇编程序",它就是
将生成的exe反编译成CIL语言的工具,如图9-4所示。

工具准备好了,接下来看一个示例程序,如代码清单9-11所示。
代码清单9-11 C#自动属性的工作原理
cs
namespace ProgrammingCSharp4
{
class ClassExample
{
static void Main()
{
Car car = new Car();
car.Speed = 10f;
double result = car.Speed;
}
}
class Car
{
// 属性
public double Speed
{
// 访问器
get;
set;
}
}
}
注意,这段代码声明了一个类Car,它有一个属性Speed,这里采用C#3.0的自动实现属性语法,可以看到,并没有单独声明一个私有字段变量(关联字段)。接下来,我们查看编译后生成的CIL代码,以了解C#编译器是如何工作的,如图9-5所示。

图9-5为编译后的Car类的成员组成图,从中可以看到:
❑编译器生成了一个私有的、类型为float64的私有字段级变量:<Speed>k__BackingField,只不过这个字段我们无法从源代码进行访问;
❑编译器生成了两个访问器方法,分别为get_Speed()和set_Speed(float64),我们特别注意到后者有一个float64类型的参数,它就是隐式的参数。
综合来看,可以得出如下结论:
❑本质上,编译器仍然使用的是和C#2.0相同的语法声明属性,即仍然使用关联字段;
❑属性的本质是方法,是一种特殊的方法。
知道了这些以后,我们来具体看一下CIL代码。阅读CIL代码有一定难度,但和汇编相比还是非常简单,因此后面会先介绍几个常见的CIL指令。话说回来,不能完全读懂这些代码也没有关系,我们的重点在于揭示工作原理,而不是学习CIL。只需要借助CIL让大家明白大致的工作原理就算达到了目的。为了帮助大家理解CIL代码,表9-5列举了几个必需的CIL指令。
在继续下文之前,我们还要强调两个概念:入栈和出栈。因为C#在本质上是基于栈的。在CIL中用来负责这个栈实现的部分叫做虚拟执行栈(Virtual Execution Stack,VES)。在下面的CIL代码中,你将看到CIL提供了一系列指令来完成将值压入到栈中,这个过程叫加载(load)。另外,CIL也定义了一系列指令来将栈顶的值移到内存中(例如局部变量),这个过程叫存储(store)。要注意的是,CIL不允许直接访问一个数据,包括局部变量、方法参数或者类的字段数据。为了实现访问,必须显式地将数据加载到栈中,并在使用时弹出。请务必注意这一点。

预备知识讲完了,我们可以尝试阅读CIL代码了,先来看Speed属性的CIL代码(对应图9-5中的②部分),如代码清单9-12所示。
代码清单9-12 Speed属性的CIL代码
html
.property instance float64 Speed()
{
.get instance float64 ProgrammingCSharp4.Car:get_Speed()
.set instance void ProgrammingCSharp4.Car:set_Speed(float64)
}//end of property Car:Speed
从上述CIL代码可以看到:
❑第3行:get访问器,调用get_Speed()方法;
❑第4行:set访问器,调用set_Speed(float64)方法,类型为float64的参数即是前面讲到的隐式值参数(value)。
我们继续看这两个访问器的CIL代码(对应图9-5中的①部分),如代码清单9-13和代码清单9-14所示。
代码清单9-13 get访问器(get_Speed方法)的CIL代码
html
.method public hidebysig specialname instance float64
get_Speed() cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (01 00 00 00)
// Code size 11 (0xb)
.maxstack 1
.locals init (float64 V_0)
IL_0000: ldarg.0
IL_0001: ldfld float64 ProgrammingCSharp4.Car::'<Speed>k__BackingField'
IL_0006: stloc.0
IL_0007: br.s IL_0009
IL_0009: ldloc.0
IL_000a: ret
} // end of method Car::get_Speed
为了突出实质内容,我们将略过无关的部分。关于上述代码的说明
如下:
❑第7行:声明一个局部变量V_0,类型为float64(也就是
double);
❑第8行:将局部变量V_0装入堆栈;
❑第9行:将编译器生成的<Speed>k__BackingField字段放入堆栈;
❑第10行:将栈中<Speed>k__BackingField的值赋给V_0变量,并
弹出栈;
❑第12行:将局部变量V_0的值放入堆栈;
❑第13行:方法返回,因为栈中有值,此值就作为返回值。
代码清单9-14 set访问器(set_Speed方法)的CIL代码
html
.method public hidebysig specialname instance void
set_Speed(float64 'value') cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (01 00 00 00)
// Code size 8 (0x8)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: stfld float64 ProgrammingCSharp4.Car::'<Speed>k__BackingField'
IL_0007: ret
} // end of method Car::set_Speed
关于上述代码的说明如下:
❑第2行:可以看到前面提到的隐式值参数(value),其类型和属性类型相同;
❑第7行:value参数的值放入栈中;
❑第9行:将栈中value参数的值赋给<Speed>k__BackingField字段;
❑第10行:方法返回,赋值结束。
现在,从更深的层次了解了C#中属性访问器的工作原理,内容比较多且有些抽象,请大家多思考、多实践,对CIL感兴趣的读者也可以自行查阅相关资料。
最后,需要注意的是,如果使用自动实现的属性,get和set访问器必须成对出现,如果只有get而没有set,如下面的代码:
cs
class Car
{
public double Speed2
{
//访问器
get;
}
}
上述代码将无法通过编译,产生的编译错误如下:
"CarCar.Speed2.get"必须声明主体,因为它未标记为abstract或extern。自动实现的属性必须同时定义get访问器和set访问器。
4.只读和只写属性
可以提供灵活的访问控制,是我们使用属性的一个重要理由之一。前面谈过只读、只写和读写属性,如下:
❑只读属性:不具有set访问器或者set访问器为private级别的属性,被视为只读属性;
❑只写属性:不具有get访问器或者get访问器为private级别的属性,被视为只写属性;
❑读写属性:具有set访问器和get访问器。
下面查看一下使用访问修饰符实现属性,语法如下:
public[数据类型][属性名]
{
访问修饰符\]get;
\[访问修饰符\]set;
}
实现一个只读属性,如代码清单9-15所示。
代码清单9-15 只读属性示例
```cs
class Car
{
// 只读属性
public double Speed
{
// 访问器
get;
private set;
}
// 只读属性
public double Speed2
{
// 访问器
get;
private set;
}
}
```
如果试图为Speed属性赋值,CLR将会引发一个异常,意为Speed属性再不可用,因为set访问器不可访问:由于set访问器不可访问,因此不能在此上下文中使用属性或索引器'ProgrammingCSharp4.Car.Speed'同理,一个只写的属性如代码清单9-16所示。
代码清单9-16 只写属性示例
```cs
// 只写属性
public double Speed
{
// 访问器
private get;
set;
}
// 只写属性
public double Speed
{
// 访问器
set; // 语法错误:C# 属性不能只有 set 访问器,需至少包含 get 或同时有 get/set
}
```
一个字段要么可读可写,要么不可读不可写,可见,仅使用字段是无法获得如此灵活的访问控制特性的。
5.执行计算
属性的访问器不但可以为关联字段赋值和返回关联字段的值,还可以根据需要加入更多的逻辑控制代码。例如,还是使用Car这个类,假如为这辆车限速120km/h,当我们试图让速度超过120km/h时,车辆拒绝加速,而将最大速度设为120km/h,用代码表示如下:
```cs
set
{
if(value>120)
{
speed=120;
}
}
```
这里就在set访问器中加入了逻辑计算功能,同理,get访问器也是一样的,比如在get访问器中进行单位换算等,下面我们给出完整的示例代码,如代码清单9-17所示。
代码清单9-17 在属性中进行逻辑计算
```cs
namespace ProgrammingCSharp4
{
class ClassExample
{
static void Main()
{
Car car = new Car();
car.Speed = 130f;
System.Console.WriteLine("当前速度为:{0}", car.Speed);
}
}
class Car
{
private double speed;
// 属性
public double Speed
{
// 访问器
get
{
return speed;
}
set
{
if (value > 120)
{
speed = 120;
}
}
}
}
}
```
说明:
❑第8行:为Speed属性设置130。
❑第26行:检测(130>120)表达式成立,拒绝接受,转而将120赋给了speed字段。
运行结果为:
当前速度为:120
6.静态属性
和前面讲到的静态变量、静态方法类似,属性也可以声明为静态,使用static关键字即可。只不过因为静态属性属于类级别,因此不能通过类的实例进行访问,也不能在静态属性中使用非静态的关联字段,示例如代码清单9-18所示。代码清单9-18 静态属性示例
```cs
namespace ProgrammingCSharp4
{
class ClassExample
{
static void Main()
{
Car.Speed = 130f;
System.Console.WriteLine("当前速度为:{0}", Car.Speed);
}
}
class Car
{
// 如果属性为静态,则关联字段也必须为静态
private static double speed;
// 静态属性
public static double Speed
{
// 访问器
get
{
return speed;
}
set
{
if (value > 120)
{
speed = 120;
}
}
}
// 自动属性
public static int Number
{
get;
set;
}
}
}
```
说明:
类Car的两个属性Speed和Number均为静态属性,其中Speed属性有关联字段speed,此关联字段同样必须为静态。
### 9.5 类的实例:对象
前面已经讲了类的成员变量和成员方法,如果要访问到这些成员,必须通过类的实例(除了静态成员以外)。前面讲过了,类是对数据和功能的封装,但封装不是目的,将类进行实例化,并使用对象的数据和服务完成某种任务才是目的。
要得到一个类的实例对象,必须先声明一个该类类型的变量,然后使用new运算符创建一个实例对象,后面会讲到,new运算符还会调用实例对象的构造函数。我们来看一个简单的例子,如代码清单9-19所示。代码清单9-19 实例化一个类
```cs
// 创建 Car 类的实例
Car car = new Car();
// 定义 Car 类
class Car
{
// 当前行驶速度(最大速度)
public double maxSpeed;
// 当前行驶方向
protected string direction;
// 已行驶距离
private double distance;
// 汽车型号(静态成员)
public static string Type;
// 无参构造函数
public Car()
{
Type = "Benz";
}
}
```
此时,这个car对象就是一个具体的汽车了,而不是概念中的汽车,如图9-6所示,对象1和对象2都是Car类的实例对象,它们各自的实例字段值均不相同,只有Type静态字段值是一样的,因为它和具体实例对象无关。

### 9.6 实例化中的内存分配
大家还记得类是值类型还是引用类型吗?如果记不太清楚了,可以翻阅之前的课程,温故而知新嘛!类是引用类型,我们知道,引用类型是在堆里分配内存的,在栈中保存的是对象的引用。因此,类的实例化涉及两个位置的内存分配。
❑在栈中为对象的引用分配空间;
❑在堆中为对象分配空间。
一起来看看下面代码中的内存是如何分配的:
Car car=new Car();
这行代码看似简单,实则不然,其过程大致可分为两个步骤:
(1)首先,声明类型为Car的变量car,并使用null初始化,此时会在栈中为car变量分配一个内存,它指向null,因为在堆中还没有创建一个car对象实例以供它指向;
(2)其次,使用new运算符在堆中创建一个新的Car实例对象,并将其引用赋予变量car,此时栈中的car变量指向新创建的实例对象(见图9-7)。

### 9.7 实例的成员
类的成员主要分两类:一类和实例相关,对于不同的实例,其值各不相同,我们把这些为特有的对象所持有的数据成员称为实例成员;另一类为静态成员,它和类相关,不为特有的对象所持有,只能通过类进行访问。
### 9.8 this关键字
this关键字主要有几个用途,如下:
❑当局部变量名称和类字段相同时,用以引用类字段;
❑将当前对象实例作为参数传递到其他方法;
❑可以声明索引器,我们将在后面课程进行说明。
注意 由于静态成员函数属于类级别,不是对象的一部分,因此在静态方法中使用this是错误的。
对于前面两种用途,我们通过实例代码进行说明,具体说明在代码注释里,如代码清单9-20所示。代码清单9-20 this关键字示例
```cs
using System;
namespace ProgrammingCSharp4
{
// 主示例类
class ClassExample
{
static void Main()
{
// 创建Car类的实例
Car car = new Car();
// 调用方法并传入参数"Chery"
car.DoSmothing("Chery");
// 输出汽车名称
Console.WriteLine(car.Name);
}
}
// 汽车类
class Car
{
// 私有字段存储汽车名称
private string name;
// 执行操作的方法
public void DoSmothing(string name)
{
// 使用this关键字区分字段和参数(同名时)
this.name = name;
// 将当前Car实例传递给CarTest的静态Test方法
CarTest.Test(this);
}
// 公开的属性,用于获取私有字段name的值
public string Name
{
get
{
return name;
}
}
}
///