构造函数的最基本的作用是为类型的一个新的实例中所有的字段和属性分配初始值。所以,根据其功能,他不需要(也没有意义)返回值。他的函数名必须和类名相同。
任何时候,只要创建类或结构的一个实例,就会调用它的构造函数。类或结构可能有多个接受不同参数的构造函数。构造函数使得程序员可设置默认值、限制实例化以及编写灵活且便于阅读的代码。
如果没有为对象提供构造函数,则默认情况下 C# 将创建一个没有任何参数的构造函数,该构造函数将会调用其基类的无参数的构造函数。如果基类也没有则继续上溯,直到object的公共构造函数,他什么都不做。(通常对于所有的值类型,上溯到system.valuetype下对应的类型例如int32等,然后为值类型赋0,对于所有的引用类型,赋null)
如果我们显式的写出了任何一个构造函数(不管是否有输入参数),c#都不会再为我们创建那个没有参数的构造函数了。
构造函数不能被继承。不能在构造函数前面加abstract, virtual, new, sealed,override。
构造函数是方法,所以也可以有存取修饰词。一般来说,构造函数都是PUBLIC的。但如果我们设定其为PRIVATE(私有构造函数),那么他就不能被外部访问。所造成的效果就是该类不能被实例化。私有构造函数是一种特殊的实例构造函数。它通常用在只包含静态成员的类中。如果类具有一个或多个私有构造函数而没有公共构造函数,则其他类(除了嵌套类)无法创建该类的实例。
构造函数之间可以通过this调用默认构造函数(没有参数的那个)。通常来说,如果这么做(构建构造函数链),则默认构造函数只能有一个,他的参数没有限制。但只能有一个的原因是所有构造函数的名字都必须相同,所以如果超过一个,this将不知道要调用哪个。
子类若不显式的调用父类的构造函数时,编译器会自动调用父类的默认(无参)构造函数。可以用Base关键字指明要调用父类的哪个构造函数。
练习:以下代码输出什么?
class="brush:csharp;gutter:true;">public class MyBaseClass { public MyBaseClass() { Console.WriteLine("In MyBaseClass()"); } public MyBaseClass(int i) { Console.WriteLine("In MyBaseClass(" + i + ")"); } } public class MyDerivedClass : MyBaseClass { public MyDerivedClass() { Console.WriteLine("In MyDerivedClass()"); } public MyDerivedClass(int i) { Console.WriteLine("In MyDerivedClass(" + i + ")"); } //public MyDerivedClass(int i, int j) //{ // Console.WriteLine("In MyDerivedClass(int i,int j)"); //} public MyDerivedClass(int i, int j) : base(i) { Console.WriteLine("In MyDerivedClass(" + i + ",int " + j+ "):base(" + i + ")"); } } class Program { static void Main(string[] args) { //Event1 MyDerivedClass myObj1 = new MyDerivedClass(); Console.WriteLine(); //Event2 MyDerivedClass myObj2 = new MyDerivedClass(4); Console.WriteLine(); //Event3 MyDerivedClass myObj3 = new MyDerivedClass(4,8); Console.WriteLine(); Console.ReadKey(); } }
解答:
Event1: 实例化myObj1,自动调用父类(再往上就是system.object,其构造函数什么都不做,下同)的无参构造函数,和自己的无参构造函数。
Event2: 实例化myObj2,自动调用父类的无参构造函数,和自己的有参构造函数。
Event3: 实例化myObj3,根据base关键字,显式的调用父类的有参构造函数,和自己的有参构造函数。
输出:
In MyBaseClass()
In MyDerivedClass()
In MyBaseClass()
In MyDerivedClass(4)
In MyBaseClass(4)
In MyDerivedClass(4,int 8):base(4)
值类型的构造函数可以存在,但系统永远不会自动的调用。我们必须显式的调用值类型的构造函数(例如结构体的)。另外,C#不允许定义值类型的无参数构造器。
对于类型中的非静态字段,类型的每一个对象都会维护字段的独立副本。相对而言,对于静态字段,所有该类型的实例共享一个值。如果要定义一个所有对象都可以分享的数据点,就可以考虑使用静态成员。
假设类中有若干个静态的字段,在构造函数中,我们初始化他们的值,这时,假设我们在外部方法中更改了静态字段的值,然后实例化一个新的类,新的类中静态字段的值会被重置为初始化的值,如果我们不希望如此而是要保持静态成员的值不变,就要借助静态构造函数。静态构造函数只会执行一次(在第一个该类型的实例被创建的时候),之后,无论再创建多少该类型的实例,都不会再次运行,这保证了类型中的静态成员的值不受影响。
静态构造函数具有以下特点:
下例选自CLR via c#, 下面代码输出什么?
class Program { static void Main(string[] args) { B b = new B(); Console.ReadKey(); } } class A { public A(string text) { Console.WriteLine(text); } } class B { static A a1 = new A("a1"); A a2 = new A("a2"); static B() { a1 = new A("a3"); } public B() { a2 = new A("a4"); } }
答案:
a1
a3
a2
a4
解释:a2和a4肯定在后面这个较好理解,因为静态构造函数先于其他构造函数执行。当创建一个新的B的实例时,先执行的是类中和静态有关的语句,然后便是静态构造函数,所以a1先于a3打印出来。下面一个例子可以看得更清楚:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 B b = new B(); 6 Console.ReadKey(); 7 } 8 } 9 10 class A 11 { 12 public A(string text) 13 { 14 Console.WriteLine(text); 15 } 16 } 17 18 class B 19 { 20 static A a1 = new A("a1"); 21 static int aProperty = 1; 22 int bProperty = 2; 23 A a2 = new A("a2"); 24 25 static B() 26 { 27 a1 = new A("a3"); 28 } 29 30 public B() 31 { 32 a2 = new A("a4"); 33 } 34 }
假如我们开启调试模式的话,那么代码(按照行号)的运行顺序将是从行19开始,然后20-13至15-21-(并不会运行非静态的部分)25-26-27-13至15-28(静态部分结束)-22-23-13至15-31-32-13至15-33-6-结束。
静态构造函数只会执行类中和静态有关的语句(先初始化类中和静态有关的变量,再执行静态函数语句)。静态构造函数只会执行一次。静态构造函数的执行契机为初始化该类型之前。
下面代码输出什么?
1 public class A 2 { 3 public static readonly int x; 4 static A() 5 { 6 x = B.y + 1; 7 } 8 } 9 10 class B 11 { 12 public static int y = A.x + 1; 13 14 static void Main(string[] args) 15 { 16 Console.WriteLine("x:{0},y:{1}。", A.x, y); 17 Console.ReadLine(); 18 } 19 }
此题非常诡异,乍一看好像无法运行,让我们慢慢分析。首先程序从类B开始运行,然后程序发现,类B拥有一个静态的成员y,于是初始化之,而又没有静态构造函数,于是先将其初始化为0。(可以将第12行拆成两行看,第一行是public static int y,第二行是y = A.x + 1)。此时y=0,然后令y等于A.x+1,程序又不知道A是啥,于是进入A类,初始化A的静态成员x为0,然后执行A的静态构造函数,此时因为B.y为0,所以可以顺利的将x设置为0+1=1。
此时程序离开A类,回到第12行,y=1+1=2,最后,打印出来结果,x=1,y=2。