首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

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#强制遵循的顺序只不过是良好的编程习惯而已。

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论