直到现在,我们讨论的所有方法、字段和属性都与一个类的实体相关联。每个对象都有这些特征的一份拷贝,并且能独立于其他对象操作它们。不过,有时我们也会需要一个对于类的所有实体都通用的特征。换而言之,与其让每一个对象拥有该特征的一份拷贝,不如有一个其值为给定类所有对象所共享的字段。C#语言通过静态特征来满足这一需求,静态特征与作为一个整体的类相关联,而不是与单独的对象相关联。
一、静态字段
创建一个对象时,我们是再创建类的一个实体,该类的字段随后用该对象特定的值填充。
假设有一些总体信息——如在校生总数——是我们希望所有Student对象能够共享的。我们可以用Student类的一个简单字段 int totalStudents和操作该字段的代码来实现,如下所示:
using System;
public class Student{
private int totalStudents;
//等等
//Property.
public int TotalStudnets{
//访问器细节从略....
}
public int ReportTotalEnrollment{
Console.WriteLine("Total Enrollment: " + TotalStudents);
}
public void IncreamentEnrollment(){
TotalsStudnets = TotalStudents +1;
}
//等等
}
这样做缺乏效率,原因有二。
首先,每个对象都要重复同样的信息。虽然一个整型(int)变量不会占用很多内存,但这仍然是一种浪费。除了存储空间的考虑之外,我们使用对象技术的任务之一,就是尽可能避免数据和/或代码的冗余。
第二,也是更值得注意的一点,当每个新的Student对象被创建时,都得调用系统中每个Student对象的IncreatEnrollment方法,确保所有的Student对象获知新的Student总数,让太让人厌烦了。
好在还有一个简单的解决方案!利用static关键字,我们可以把totalStudents设计为Student类的静态字段:
public class Student{
//totalStudents被声明为静态字段.
private static int totalStudents;
//细节从略...
public int ReportTotalEnrollment(){
Console.WriteLine("Total Enrollment: " + TotalStudents);
}
public void IncrementEnrollment(){
totalStudents = totalStudnets +1;
}
}
静态字段的值为一个类的所有实体所共享;在概念上,它的值属于类所有,而不是类的每一个实体/对象所有。
每个Student对象都能访问和修改共享的totalStudents字段,就像它是非共享字段一样;在早前的代码例子中,ReportTotalEnrollment和IncrementEnrollment方法操作totalStudent字段时,看起来和Student类的其他方法没有区别。不同之处在于,静态字段的值是共享的,所以,如果我们执行下面的客户代码:
Student s1 = new Student();
s1.Name = "Fred";
s1.IncrementEnrollment();
Student s2 = new Student();
s2.Name = "Mary";
s2.IncrementEnrollment();
Student s3= new Student();
s3.Name = "Mary";
s3.IncrementEnrollment();
则最后totalStudents的值大概就会这样变化(假定从0开始计算):
1、当s1接收到消息s1.IncrementEnrollment()时,totalStudents的共享值增加1(从0增加到1);
2、当s2接收到消息s2.IncrementEnrollment()时,totalStudents的共享值增加1(从1增加到2);
3、 当s3接收到消息s3.IncrementEnrollment()时,totalStudents的共享值增加1(从2增加到3);
在此之后,如果其中任何一个对象检查totalStudents的值,都会得到3。也就是说,我们调用s1、s2或s3.IncrementEnrollment方法得到的结果是一样的:每个方法调用的输出信息都会相同。扩展一下之前的例子:
Student s1 = new Student();
s1.Name = "Fred";
s1.IncrementEnrollment();
Student s2 = new Student();
s2.Name = "Mary";
s2.IncrementEnrollment();
Student s3= new Student();
s3.Name = "Mary";
s3.IncrementEnrollment();
s1.ReportTotalEnrollment();
s2.ReportTotalEnrollment();
s2.ReportTotalEnrollment();
执行上述代码得到的结果:
Total Enrollment:3
Total Enrollment:3
Total Enrollment:3
二、静态属性
我们多半会将静态字段totalStudents声明为私有(和其他字段一样),为它编写公共访问器。和使用static关键字声明静态字段不同,静态属性和非静态属性没有什么不同:
public class Student{
private static int totalStudents;
//细节从略.
//为静态字段声明公共静态属性
public static int TotalStudnets{
get{
return totalStudents;
}
set{
totalStudents = value;
}
}
//细节从略
public void IncrementEnrollment(){
//现在可以使用取值/赋值访问器了.
//注意下面代码使用了大写字母T.
TotalStudents = TotalStudents +1;
}
}
定义静态属性的取值和复制访问器,和定义非静态属性访问器,方式都一样:取值访问器的返回值隐含地与属性类型保持一致,而赋值访问器的返回类型则是void,同时被传入一个名为value的参数。
静态属性不能通过单个对象访问,但可以使点符号,通过整个类调用:
Console.WriteLine("Total Enrollment =" + Student.TotalStudents);
如果试图错误地通过对象调用一个静态属性:
Student s1 = new Student();
Console.WriteLine("Total Enrollment =" + s1.TotalStudents);
编译器将产生以下错误信息:
静态方法:
静态字段和静态属性与类相关联,而不是关联到具体的单个对象;同样,静态方法也可以通过作为整体的类来调用。
我们吧IncrementEnrollment和ReportTotalErrollment方法声明为静态方法:
public class Student
{
private static int totalStudents;
public static int TotalStudents{
get{
return totalStudents;
}
set{
totalStudents = value;
}
}
//两种方法都是静态方法
public static void IncrementEnrollment(){
//和非静态方法相比,方法体没有变化。
TotalStudents = TotalStudents +1;
}
public static int ReportTotalEnrollment(){
//一样的
Console.WriteLine("Total Enrollment: " + TotalStudents);
}
//等等
}
和静态属性一样,静态方法也只能通过作为一个整体的类来调用:
Student.IncrementEnrollment();
不能通过单个对象引用调用静态方法;如果错误地通过一个Student对象调用了IncrementEnrollment方法:
Student s1 = new Student();
s1.IncrementEnrollment(); //不能被编译; IncrementEnrollment是静态方法
编译器将产生一下错误:
Error cs0176: Static member' Sudent.IncrementEnrollment()' Cannot be accessed with an instance reference;qualify it with a type name instend
再次修改客户代码的例子,调用静态方法IncrementEnrollment和ReportTotalEnrollment方法,得到一下代码:
Student s1 = new Student();
s1.Name = "Fred";
Student.IncrementEnrollment();
Student s2 = new Student();
s2.Name = "Mary";
Student.IncrementEnrollment();
Student s3= new Student();
s3.Name = "Mary";
Student.IncrementEnrollment();
Student.ReportEnrollment();
输出结果如下:
Total Enrollment =3
静态方法的限制
注意,静态方法访问所属类的字段时,有一个重要的限制:它们不能访问类的非静态字段。如果尝试编写类似下面的Print静态方法,该方法试图访问类似name这样的非静态字段,编译器会阻止我们这样做。
public class Student
{
//两个字段,一个是静态的,一个不是
private string name;
private static int totalStudents;
//等等
public string Name{
get;set;
}
public static int TotalStudnets{
get;set;
}
public static void Print()
{
//静态方法不能访问“name”这样的非静态字段,下面的代码不能被编译。
Console.WriteLine(Name + " is one of " + TotalStudents + "students.");
}
}
为什么会这样?在非静态字段被赋值之前,类还是一个空模板,直至我们实例化这一个对象,而且填充其(非静态)字段的值。
如果通过作为整体的类调用了静态方法,而且该方法试图访问一个非静态字段,这个字段的值是未被定义的(非静态字段的值在类的上下文中是未被定义的)。
关于静态方法还有两个限制。
1、不能被派生类覆载,所以不可以给静态方法加上vitual关键字:
2、静态方法也不能被声明为抽象方法;
常量
常量是一种给定初始值后就不会变化的变量。我们使用const关键字来声明常量,如下所示:
public const double FahrenheitFreezing = 32.0;
1、常量隐含地是静态的(可以通过类直接调用),所以不应给常量的声明加上static关键字,否则会出现编译错误。
2、在声明常量时必须给定一个值:即,不能在声明产量后又在程序的其他位置修改它的值。如果我们试图声明没有初始化的常量:
public const double FahrenheitFreezing ,编译器就会报错