背景:                 
[本书目录] [图书首页] [本书讨论区]  
链接地址:http://www.17xie.com/read-58449.html    注册17xie 一起来写书 实现您的出书梦想!

3.2  类成员

类中的数据和函数称为类的成员。Microsoft的正式术语对数据成员和函数成员进行了区分。除了这些成员外,类还可以包含嵌套的类型(例如其他类)。类中的所有成员都可以声明为public(此时可以在类的外部直接访问它们)private(此时,它们只能由类中的其他代码来访问)。与VBC++Java一样,C#在这个方面还有变化,例如protected(表示成员仅能由该成员所在的类及其派生类访问),第4章将详细解释各种访问级别。

3.2.1  数据成员

数据成员包含了类的数据—— 字段、常量和事件。数据成员可以是静态数据(与整个类相关)或实例数据(类的每个实例都有它自己的数据副本)。通常,对于面向对象的语言,类成员总是实例成员,除非用static进行了显式的声明。

字段是与类相关的变量。在前面的例子中已经使用了PhoneCustomer类中的字段:

一旦实例化PhoneCustomer对象,就可以使用语法Object.FieldName来访问这些字段:

PhoneCustomer Customer1 = new PhoneCustomer();

Customer1.FirstName = "Simon";

常量与类的关联方式同变量与类的关联方式一样。使用const关键字来声明常量。如果它们声明public,就可以在类的外部访问。

class PhoneCustomer

{

   public const string DayOfSendingBill = "Monday";

   public int CustomerID;

   public string FirstName;

   public string LastName;

}

事件是类的成员,在发生某些行为(例如改变类的字段或属性,或者进行了某种形式的用户交互操作)时,它可以让对象通知调用程序。客户可以包含称为“事件处理程序”的代码来响应该事件。第6章将详细介绍事件。

3.2.2  函数成员

函数成员提供了操作类中数据的某些功能,包括方法、属性、构造函数和终结器(finalizer)、运算符以及索引器。

方法是与某个类相关的函数,它们可以是实例方法,也可以是静态方法。实例方法处理类的某个实例,静态方法提供了更一般的功能,不需要实例化一个类(例如Console.WriteLine()方法)。下一节介绍方法。

属性是可以在客户机上访问的函数组,其访问方式与访问类的公共字段类似。C#为读写类上的属性提供了专用语法,所以不必使用那些名称中嵌有GetSet的偷工减料的方法。因为属性的这种语法不同于一般函数的语法,在客户代码中,虚拟的对象被当做实际的东西。

构造函数是在实例化对象时自动调用的函数。它们必须与所属的类同名,且不能有返回类型。构造函数用于初始化字段的值。

终结器类似于构造函数,但是在CLR检测到不再需要某个对象时调用。它们的名称与类相同,但前面有一个~符号。C++程序员应注意,终结器在C#中比在C++中用得少得多,因为CLR会自动进行垃圾收集,另外,不可能预测什么时候调用终结器。第7章将介绍终结器。

运算符执行的最简单的操作就是+和–。在对两个整数进行相加操作时,严格地说,就是对整数使用+运算符。C#还允许指定把已有的运算符应用于自己的类(运算符重载)。第5章将详细论述运算符。

索引器允许对象以数组或集合的方式进行索引。第5章介绍索引器。

1. 方法

VBCC++中,可以定义与类完全不相关的全局函数,但在C#中不能这样做。在C#中,每个函数都必须与类或结构相关。

注意,正式的C#术语实际上并没有区分函数和方法。在这个术语中,“函数成员”不仅包含方法,而且也包含类或结构的一些非数据成员。它包括索引器、运算符、构造函数和析构函数等,甚至还有属性。这些都不是数据成员,字段、常量和事件才是数据成员。本章将详细讨论方法。

(1) 方法的声明

C#中,定义方法的语法与C风格的语言相同,与C++Java中的语法也相同。与C++的主要语法区别是,在C#中,每个方法都单独声明为publicprivate,不能使用public:块把几个方法定义组合起来。另外,所有的C#方法都在类定义中声明和定义。在C#中,不能像在C++中那样把方法的实现代码分隔开来。

C#中,方法的定义包括方法的修饰符(例如方法的可访问性)、返回值的类型,然后是方法名、输入参数的列表(用圆括号括起来)和方法体(用花括号括起来)

[modifiers] return_type MethodName([parameters])

{

   // Method body

}

每个参数都包括参数的类型名及在方法体中的引用名称。但如果方法有返回值,return语句就必须与返回值一起使用,以指定出口点,例如:

public bool IsSquare(Rectangle rect)

{

   return (rect.Height == rect.Width);

}

这段代码使用一个表示矩形的.NET基类System.Drawing.Rectangle

如果方法没有返回值,就把返回类型指定为void,因为不能省略返回类型。如果方法不带参数,仍需要在方法名的后面写上一对空的圆括号()(就像本章前面的Main()方法)。此时return语句就是可选的—— 当到达右花括号时,方法会自动返回。注意方法可以包含任意多个return语句:

public bool IsPositive(int value)

{

   if (value < 0)

      return false;

   return true;

}

(2) 调用方法

C#中调用方法的语法与C++Java中的一样,C#VB的惟一区别是在C#中调用方法时,必须使用圆括号,这要比VB 6中有时需要括号,有时不需要括号的规则简单一些。

下面的例子MathTest说明了类的定义和实例化、方法的定义和调用的语法。除了包含Main()方法的类之外,它还定义了类MathTest,该类包含两个方法和一个字段。

using System;

 

namespace Wrox.ProCSharp. MathTestSample

{

   class MainEntryPoint

   {

      static void Main()

      {

         // Try calling some static functions

         Console.WriteLine("Pi is " + MathTest.GetPi());

         int x = MathTest.GetSquareOf(5);

         Console.WriteLine("Square of 5 is " + x);

 

         // Instantiate at MathTest object

         MathTest math = new MathTest();         // this is C#'s way of

                                             // instantiating a reference type

        

         // Call non-static methods

         math.value = 30;

         Console.WriteLine(

            "Value field of math variable contains " + math.value);

         Console.WriteLine("Square of 30 is " + math.GetSquare());

      }

   }

 

   // Define a class named MathTest on which we will call a method

   class MathTest

   {

      public int value;

 

      public int GetSquare()

      {

         return value*value;

      }

 

      public static int GetSquareOf(int x)

      {

         return x*x;

      }

 

      public static double GetPi()

      {

         return 3.14159;

      }

   }

}

运行mathTest示例,会得到如下结果:

csc MathTest.cs

Microsoft (R) Visual C# .NET Compiler version 8.00.40607.16

for Microsoft (R) .NET Framework version 2.0.40607

Copyright (C) Microsoft Corporation 2001-2003. All rights reserved.

 

MathTest.exe

Pi is 3.14159

Square of 5 is 25

Value field of math variable contains 30

Square of 30 is 900

从代码中可以看出,MathTest类包含一个字段和一个方法,该字段包含一个数字,该方法计算数字的平方。这个类还包含两个静态方法,一个返回pi的值,另一个计算把作为参数传入的数字的平方。

这个类有一些功能并不是C#程序设计的好例子。例如,GetPi()通常作为const字段来执行,而好的设计应使用目前还没有介绍的概念。

C++Java开发人员应很熟悉这个例子的大多数语法。如果您有VB的编程经验,只需把MathTest类看作一个执行字段和方法的VB类模块。但无论使用什么语言,都要注意两个要点。

(3) 给方法传递参数

参数可以通过引用或值传递给方法。在变量通过引用传递给方法时,被调用的方法得到的就是这个变量,所以在方法内部对变量进行的任何改变在方法退出后仍旧发挥作用。而如果变量是通过值传送给方法的,被调用的方法得到的是变量的一个副本,也就是说,在方法退出后,对变量进行的修改会丢失。对于复杂的数据类型,按引用传递的效率更高,因为在按值传递时,必须复制大量的数据。

C#中,所有的参数都是通过值来传递的,除非特别说明。这与C++是相同的,但与VB相反。但是,在理解引用类型的传递过程时需要注意。因为引用类型的对象只包含对象的引用,它们只给方法传递这个引用,而不是对象本身,所以对底层对象的修改会保留下来。相反,值类型的对象包含的是实际数据,所以传递给方法的是数据本身的副本。例如,int通过值传递给方法,方法对该int的值所作的任何改变都没有改变原int对象的值。但如果数组或其他引用类型(如类)传递给方法后,方法会使用该引用改变这个数组中的值,而新值会反射到原来的数组对象上。

下面的例子ParameterTest.cs说明了这一点:

using System;

 

namespace Wrox.ProCSharp. ParameterTestSample

{

   class ParameterTest

   {

      static void SomeFunction(int[] ints, int i)

      {

         ints[0] = 100;

         i = 100;

      }

  

      public static int Main()

      {

         int i = 0;

         int[] ints = { 0, 1, 2, 4, 8 };

         // Display the original values

         Console.WriteLine("i = " + i);

         Console.WriteLine("ints[0] = " + ints[0]);

         Console.WriteLine("Calling SomeFunction...");

 

         // After this method returns, ints will be changed,

         // but i will not

         SomeFunction(ints, i);

         Console.WriteLine("i = " + i);

         Console.WriteLine("ints[0] = " + ints[0]);

         return 0;

      }

   }

}

结果如下:

csc ParameterTest.cs

Microsoft (R) Visual C# .NET Compiler version 8.00.40607.16

for Microsoft (R) .NET Framework version 2.0.40607

Copyright (C) Microsoft Corporation 2001-2003. All rights reserved.

 

ParameterTest

i = 0

ints[0] = 0

Calling SomeFunction...

i = 0

ints[0] = 100

注意,i的值保持不变,而在ints中改变的值在原来的数组中也改变了。

注意字符串是不同的,因为字符串是不能改变的(如果改变字符串的值,就会创建一个全新的字符串),所以字符串无法显示一般引用类型的行为方式。在方法调用中,对字符串所作的任何改变都不会影响原来的字符串。这一点将在第8章详细讨论。

 

(4) ref参数

通过值传送变量是默认的,也可以迫使值参数通过引用传送给方法。为此,要使用ref关键字。如果把一个参数传递给方法,且这个方法的输入参数前带有ref关键字,则该方法对变量所作的任何改变都会影响原来对象的值:

static void SomeFunction(int[] ints, ref int i)

{

   ints[0] = 100;

   i = 100;       //the change to i will persist after SomeFunction() exits

}

在调用该方法时,还需要添加ref关键字:

SomeFunction(ints, ref i);

C#中添加ref关键字等同于在C++中使用&语法指定按引用传递参数。但是,C#在调用方法时要求使用ref关键字,使操作更明确(因此有助于防止错误)

最后,C#仍要求对传递给方法的参数进行初始化,理解这一点也是非常重要的。在传递给方法之前,无论是按值传递,还是按引用传递,都必须初始化变量。

(5) out关键字

C风格的语言中,函数能从一个例程中输出多个值,这种情况很常见。这是使用输出参数实现的——方法是把输出值赋给通过引用传递给方法的变量。通常,变量通过引用传送的初值是不重要的,这些值会被函数重写,函数甚至从来没有使用过它们。

如果可以在C#中使用这种约定,就会非常方便。但C#要求变量在被引用前必须用一个初值进行初始化。在把输入变量传递给函数前,可以用没有意义的值初始化它们,函数将使用真实、有意义的值初始化它们,这样做是没有必要的,有时甚至会引起混乱。但有一种方法能够简化C#编译器所坚持的输入参数的初始化。

编译器使用out关键字来初始化。当在方法的输入参数前面加上out关键字时,传递给该方法的变量可以不初始化。该变量通过引用传送,所以在从被调用的方法中返回时,方法对该变量进行的任何改变都会保留下来。在调用该方法时,还需要使用out关键字,这正如在定义该方法时一样:

static void SomeFunction(out int i)

{

   i = 100;

}

 

public static int Main()

{

   int i; // note how i is declared but not initialized

   SomeFunction(out i);

   Console.WriteLine(i);

   return 0;

}

out关键字是C#中的新增内容,在VBC++中没有对应的关键字,该关键字的引入使C#更安全,更不容易出错。如果在函数体中没有给out参数分配一个值,该方法就不能编译。

(6) 方法的重载

C#支持方法的重载——方法的几个有不同签名(名称、参数个数、参数类型)的版本,但不支持C++VB中的默认参数。为了重载方法,只需声明同名但参数个数或类型不同的方法即可:

class ResultDisplayer

{

   void DisplayResult(string result)

   {

      // implementation

   }

 

   void DisplayResult(int result)

   {

      // implementation

   }

}

因为C#不直接支持可选参数,所以需要使用方法重载来达到此目的:

class MyClass

{

   int DoSomething(int x)   // want 2nd parameter with default value 10

   {

      DoSomething(x, 10);

   }

 

   int DoSomething(int x, int y)

   {

      // implementation

   }

}

在任何语言中,对于方法重载来说,如果调用了错误的重载方法,就有可能出现运行错误。第4章将讨论如何使代码避免这些错误。现在,知道C#在重载方法的参数方面有一些小区别即可:

       两个方法不能仅在返回类型上有区别。

       两个方法不能仅根据参数是声明为ref还是out来区分。

2. 属性

属性(property)不太常见,因为它们表示的概念是C#VB中提取的,而不是从C++/Java中提取的。属性的概念是:它是一个方法或一对方法,在客户机代码看来,它们是一个字段。例如Windows窗体的Height属性。假定有下面的代码:

// mainForm is of type System.Windows.Form

mainForm.Height = 400;

执行这段代码,窗口的高度设置为400,因此窗口会在屏幕上重新设置大小。在语法上,上面的代码类似于设置一个字段,但实际上是调用了属性访问器,它包含的代码重新设置了窗体的大小。

C#中定义属性,可以使用下面的语法:

public string SomeProperty

{

   get

   {

      return "This is the property value";

   }

   set

   {

      // do whatever needs to be done to set the property

   }

}

get访问器不带参数,且必须返回属性声明的类型。也不应为set访问器指定任何显式参数,但编译器假定它带一个参数,其类型也与属性相同,并表示为value。例如,下面的代码包含一个属性ForeName,它设置了一个字段foreName,该字段有一个长度限制。

private string foreName;

 

public string ForeName

{

   get

   {

      return foreName;

   }

   set

   {

      if (value.Length > 20)

         // code here to take error recovery action

         // (eg. throw an exception)

      else

         foreName = value;

   }

}

注意这里的命名模式。我们采用C#的区分大小写模式,使用相同的名称,但公共属性采用Pascal大小写命名规则,而私有属性采用camel大小写命名规则。一些开发人员喜欢使用前面有下划线的字段名_foreName,这会为识别字段提供极大的便利。

VB6程序员应注意,C#不区分VB6中的SetLet,在C#中,写入访问器总是用关键字set标识。

(1) 只读和只写属性

在属性定义中省略set访问器,就可以创建只读属性。因此,把上面例子中的ForeName变成只读属性:

private string foreName;

 

public string ForeName

{

   get

   {

      return foreName;

   }

}

同样,在属性定义中省略get访问器,就可以创建只写属性。但是,这是不好的编程方式,因为这可能会使客户机代码的作者感到迷惑。一般情况下,如果要这么做,最好使用一个方法替代。

(2) 属性的访问修饰符

C#允许给属性的getset 访问器设置不同的访问修饰符,所以属性可以有公共的get访问器和私有或受保护的set访问器。这有助于控制属性的设置方式或时间。在下面的例子中,注意set访问器有一个私有访问修饰符,而get访问器没有任何访问修饰符。这表示get访问器具有属性的访问级别。在get set 访问器中,必须有一个具备属性的访问级别。如果get访问器的访问级别是protected,就会产生一个编译错误,因为这会使两个访问器的访问级别都不是属性。

public string Name

{

   get

   {

      return _name;

   }

   set

   {

      _name = value;

   }

}

(3) 内联

一些开发人员可能会担心,在上一节中,我们列举了标准C#编码方式导致了非常小的函数的许多情形,例如通过属性访问字段,而不是直接访问字段。这些额外的函数调用是否会增加系统开销,导致性能下降?其实,不需要担心这种编程方式会在C#中带来性能损失。C#代码会编译为IL,然后在运行期间进行正常的JIT编译,获得内部可执行代码。JIT编译器可生成高度优化的代码,并在适当的时候内联代码(即用内联代码来替代函数调用)。如果某个方法或属性的执行代码仅是调用另一个方法,或返回一个字段,则该方法或属性肯定是内联的。但要注意,在何处内联代码的决定完全由CLR做出。我们无法使用像C++inline这样的关键字来控制哪些方法是内联的。

3. 构造函数

C#中声明基本构造函数的语法与在Java C++中相同。下面声明一个与包含的类同名的方法,但该方法没有返回类型:

public class MyClass

{

   public MyClass()

   {

   }

   // rest of class definition

Java C++相同,没有必要给类提供构造函数,在我们的例子中没有提供这样的构造函数。一般情况下,如果没有提供任何构造函数,编译器会在后台创建一个默认的构造函数。这是一个非常基本的构造函数,它只能把所有的成员字段初始化为标准的默认值(例如,引用类型为空引用,数字数据类型为0boolfalse)。这通常就足够了,否则就需要编写自己的构造函数。

注意:

对于C++程序员来说,C#中的基本字段在默认情况下初始化为0,而C++中的基本字段不进行初始化,不需要像C++那样频繁地在C#中编写构造函数。

构造函数的重载遵循与其他方法相同的规则。换言之,可以为构造函数提供任意多的重载,只要它们的签名有明显的区别即可:

   public MyClass()   // zero-parameter constructor

   {

      // construction code

   }

   public MyClass(int number)   // another overload

   {

      // construction code

   }

但注意,如果提供了带参数的构造函数,编译器就不会自动提供默认的构造函数,只有在没有定义任何构造函数时,编译器才会自动提供默认的构造函数。在下面的例子中,因为定义了一个带一个参数的构造函数,所以编译器会假定这是可以使用的惟一构造函数,不会隐式地提供其他构造函数:

public class MyNumber

{

   private int number;

   public MyNumber(int number)  

   {

      this.number = number;

   }

}

上面的代码还说明,一般使用this关键字区分成员字段和同名的参数。如果试图使用无参数的构造函数实例化MyNumber对象,就会得到一个编译错误:

MyNumber numb = new MyNumber();   // causes compilation error

注意,可以把构造函数定义为privateprotected,这样不相关的类就不能访问它们:

public class MyNumber

{

   private int number;

   private MyNumber(int number)   // another overload

   {

      this.number = number;

   }

}

在这个例子中,我们并没有为MyNumber定义任何公共或受保护的构造函数。这就使MyNumber不能使用new运算符在外部代码中实例化(但可以在MyNumber上编写一个公共静态属性或方法,以进行实例化)。这在下面两种情况下是有用的:

       类仅用作某些静态成员或属性的容器,因此永远不会实例化。

       希望类仅通过调用某个静态成员函数来实例化(这就是所谓对象实例化的类代理方法)

(1) 静态构造函数

C#的一个新特征是也可以给类编写无参数的静态构造函数。这种构造函数只执行一次,而前面的构造函数是实例构造函数,只要创建类的对象,它都会执行。静态构造函数在C++VB6中没有对应的函数。

class MyClass

{

   static MyClass()

   {

      // initialization code

   }

   // rest of class definition

}

编写静态构造函数的一个原因是,类有一些静态字段或属性,需要在第一次使用类之前,从外部源中初始化这些静态字段和属性。

.NET运行库没有确保静态构造函数什么时候执行,所以不要把代码放在某个特定的时刻(例如,加载程序集时)执行的静态构造函数中。也不能预计不同类的静态构造函数按照什么顺序执行。但是,可以确保静态构造函数至多运行一次,即在代码引用类之前执行。在C#中,静态构造函数通常在第一次调用类的成员之前执行。

注意,静态构造函数没有访问修饰符,其他C#代码从来不调用它,但在加载类时,总是由.NET运行库调用它,所以像publicprivate这样的访问修饰符就没有意义了。同样,静态构造函数不能带任何参数,一个类也只能有一个静态构造函数。很显然,静态构造函数只能访问类的静态成员,不能访问实例成员。

注意,无参数的实例构造函数可以在类中与静态构造函数安全共存。尽管参数列表是相同的,但这并不矛盾,因为静态构造函数是在加载类时执行,而实例构造函数是在创建实例时执行,所以构造函数的执行不会有冲突。

如果多个类都有静态构造函数,先执行哪个静态构造函数是不确定的。此时应根据其他静态构造函数的执行情况,在静态构造函数中添加代码。另一方面,如果静态字段有默认值,它们就在调用静态构造函数之前指定。

下面用一个例子来说明静态构造函数的用法。假定这个例子叫StaticConstructorSample,基于包含用户设置的程序(假定存储在某个配置文件中)。为了简单一些,假定只有一个用户设置—— BackColor,表示要在应用程序中使用的背景色。因为这里不想编写从外部数据源中读取数据的代码,所以假定该设置在工作日的背景色是红色,在周末的背景色是绿色。程序仅在控制台窗口中显示设置—— 但这足以说明静态构造函数是如何工作的。

namespace Wrox.ProCSharp.StaticConstructorSample

{

   public class UserPreferences

   {

      public static readonly Color BackColor;

 

      static UserPreferences()

      {

         DateTime now = DateTime.Now;

         if (now.DayOfWeek == DayOfWeek.Saturday

            || now.DayOfWeek == DayOfWeek.Sunday)

            BackColor = Color.Green;

         else

            BackColor = Color.Red;

      }

 

      private UserPreferences()

      {

      }

   }

}

这段代码说明了颜色设置如何存储在静态变量中,该静态变量在静态构造函数中进行初始化。把这个字段声明为只读类型,表示其值只能在构造函数中设置。本章后面将详细介绍只读字段。这段代码使用了MicrosoftFramework类库中支持的两个有用的结构System.DateTimeSystem.Drawing.ColorDateTime结构实现了静态属性Now和实例属性DayOfWeekNow属性返回当前的时间,DayOfWeek属性可以计算出某个日期是星期几。Color(详见第25)用于存储颜色,它实现了各种静态属性,例如本例使用的RedGreen,返回常用的颜色。为了使用Color结构,需要在编译时引用System.Drawing.dll程序集,且必须为System.Drawing命名空间添加一个using语句:

using System;

using System.Drawing;

用下面的代码测试静态构造函数:

   class MainEntryPoint

   {

      static void Main(string[] args)

      {

         Console.WriteLine("User-preferences: BackColor is: " +

                            UserPreferences.BackColor.ToString());

      }

   }

编译并运行这段代码,会得到如下结果:

C:>StaticConstructor

User-preferences: BackColor is: Color [Red]

(2) 从其他构造函数中调用构造函数

有时,在一个类中有几个构造函数,以容纳某些可选参数,这些构造函数都包含一些共同的代码。例如,下面的情况:

class Car

{

   private string description;

   private uint nWheels;

   public Car(string model, uint nWheels)

   {

      this.description = description;

      this.nWheels = nWheels;

   }

 

   public Car(string model)

   {

      this.description = description;

      this.nWheels = 4;

   }

// etc.

这两个构造函数初始化了相同的字段,显然,最好把所有的代码放在一个地方。C#有一个特殊的语法,称为构造函数初始化器,可以实现此目的:

class Car

{

   private string description;

   private uint nWheels;

 

   public Car(string model, uint nWheels)

   {

      this.description = description;

      this.nWheels = nWheels;

   }

 

   public Car(string model) : this(model, 4)

   {

   }

   // etc  

这里,this关键字仅调用参数最匹配的那个构造函数。注意,构造函数初始化器在构造函数之前执行。现在假定运行下面的代码:

Car myCar = new Car("Proton Persona");

在本例中,在带一个参数的构造函数执行之前,先执行带2个参数的构造函数(但在本例中,因为带一个参数的构造函数没有代码,所以没有区别)

C#构造函数初始化符可以包含对同一个类的另一个构造函数的调用(使用前面介绍的语法),也可以包含对直接基类的构造函数的调用(使用相同的语法,但应使用base关键字代替this)。初始化符中不能有多于一个的调用。

C#中,构造函数初始化符的语法类似于C++中的构造函数初始化列表,但C++开发人员要注意,除了语法类似之外,C#初始化符所包含的代码遵循完全不同的规则。可以使用C++初始化列表指定成员变量的初始值,或调用基类构造函数,而C#初始化符中的代码只能调用另一个构造函数。这就要求C#类在构造时遵循严格的顺序,但C++就没有这个要求。这个问题详见第4章,那时就会看到,C#强制遵循的顺序只不过是良好的编程习惯而已。

3.2.3  只读字段

常量的概念就是一个包含不能修改的值的变量,常量是C#与大多数编程语言共有的。但是,常量不必满足所有的要求。有时可能需要一些变量,其值不应改变,但在运行之前其值是未知的。C#为这种情形提供了另一个类型的变量:只读字段。

readonly关键字比const灵活得多,允许把一个字段设置为常量,但可以执行一些运算,以确定它的初始值。其规则是可以在构造函数中给只读字段赋值,但不能在其他地方赋值,只读字段还可以是一个实例字段,而不是静态字段,类的每个实例可以有不同的值。与const字段不同,如果要把只读字段设置为静态,就必须显式声明。

如果有一个编辑文档的MDI程序,因为要注册,需要限制可以同时打开的文档数。现在假定要销售该软件的不同版本,而且顾客可以升级他们的版本,以便同时打开更多的文档。显然,不能在源代码中对最大文档数进行硬编码。而是需要一个字段表示这个最大文档数。这个字段必须是只读的——每次安装程序时,从注册表键或其他文件存储中读取。代码如下所示:

   public class DocumentEditor

   {

      public static readonly uint MaxDocuments;

 

      static DocumentEditor()

      {

         MaxDocuments = DosomethingToFindOutMaxNumber();

      }

在本例中,字段是静态的,因为每次运行程序的实例时,只需存储最大文档数一次。这就是在静态构造函数中初始化它的原因。如果只读字段是一个实例字段,就要在实例构造函数中初始化它。例如,假定编辑的每个文档都有一个创建日期,但不允许用户修改它(因为这会覆盖过去的日期)。注意,该字段也是公共的,我们不需要把只读字段设置为私有,因为按照定义,它们不能在外部修改(这个规则也适用于常量)

如前所述,日期用基类System.DateTime表示。下面的代码使用带有3个参数(年份、月份和月份中的日)System.DateTime构造函数,可以从MSDN文档中找到这个构造函数和其他DateTime构造函数的更多信息。

   public class Document

   {

      public readonly DateTime CreationDate;

 

      public Document()

      {

         // read in creation date from file. Assume result is 1 Jan 2002

         // but in general this can be different for different instances

         // of the class 

         CreationDate = new DateTime(2002, 1, 1);

         }

      }

在上面的代码中,CreationDateMaxDocuments的处理方式与其他字段相同,但因为它们是只读的,所以不能在构造函数外部赋值:

void SomeMethod()

{

   MaxDocuments = 10;       // compilation error here. MaxDocuments is readonly

}

还要注意,在构造函数中不必给只读字段赋值,如果没有赋值,它的值就是其数据类型的默认值,或者在声明时给它初始化的值。这适用于静态和实例只读字段。


字数:21602    最后更新:7个月以前 [04-10 21:07]happyskynet 修改
本页编辑者:happyskynet  
[前一页]:第3章 对象和类型  [后一页]:3.3 结构
[在本页中加入书签] [收藏本书] [推荐本书]
  17xie论坛 > 本书讨论区 > 本页评论   (共0条)
发表评论

用户名称 匿名发表
评论内容
验证码

关于我们 | 版权声明 | 免责声明 | 诚聘英才 | 联系我们 | 合作伙伴 | 友情链接 | 广告合作 | 提交意见
Copyright © 2007 17xie.com 互联网协同写书平台 京ICP备08002671号