c#类的成员初始化顺序

文章一:
《Effective C#》:使用成员初始化语句
 

  为了方便内容的开展,我先说说一个对象的构造过程。

  对于类型第一个实例的构造过程大致如下:

  1. 分配静态成员的内存空间,此时空间存储数据为0;
  2. 执行静态成员的初始化语句;
  3. 执行基类的静态构造函数;
  4. 执行类型的静态构造函数;

  5. 分配成员的内存空间,此时空间存储数据为0;
  6. 执行成员的初始化语句;
  7. 执行相应的基类构造函数;
  8. 执行类型的构造函数。

  那么对于同类型的后续创建对象,前4个步骤不用执行的,直接从第5步开始。

  现在来说说为什么推荐使用成员初始化语句来初始化成员。由于成员初始化先于构造函数的调用,所以更早初始化有利于使用;其次,避免对构造函数重复添加初始化代码,尤其是新增成员的时候,把初始化放到定义成员的位置,减少因构造函数之间的不一致,而造成某些成员未被初始化。而且把成员初始化从构造函数中抽出来,使代码显得更简洁明朗。

  例如:

public class MyList
{
 //Init class member here
  private ArrayList _List = new ArrayList();
}

  是不是所有的成员都可以这样进行初始化呢。事实上,有三种场景是不适合用这样的方式来完成成员初始化。

  第一种就是给成员赋给“0”或者“null”,这并不是错误语句,而是没有必要的。参看前面的对象构造过程,由于成员首先会被分配内存空间,并且同时已经用“0”进行初始化了。因此显式的赋值会增加指令操作,而影响效率。

  第二种就是根据不同参数来指明成员初始化的方式,而一般类似操作是放在构造函数中。如果使用成员初始化语句的话,那么在构造函数中重新初始化成员,就会生成一个短暂的临时对象。

  例如:

public class MyList
{
 //Init class member here

 private ArrayList _List = new ArrayList();
 public MyList()
 {}
 public MyList( int nSize )
 {
  _List = new ArrayList( nSize );
 }
}

  以上例子,如果通过“MyList( int nSize )”这个构造函数来创建对象,会先通过成员初始化来初始“_List”成员,然后在构造函数重新初始化此成员。那么实际上,会在内存中产生两个ArrayList对象,前一个已经成为垃圾,需要等待GC进行回收。对于这种情况,用如下的方式可能会更好。

public class MyList
{
 //Init class member here
 private ArrayList _List;

 public MyList()
 {
  _List = new ArrayList();
 }
 public MyList( int nCount )
 {
  _List = new ArrayList( nCount );
 }
}

  对于最后一种就是,由于通过成员初始化来初始化成员,在初始化语句地方是不能加入try-catch来捕获异常,因此在初始化过程中所出现的异常会被传递出去,为了保证程序的安全,则需要在调用端进行try-catch捕获。不过这样操作会使调用端的代码显得繁琐,更合理的做法,是在类型中对成员初始化进行异常处理,因此采用构造函数来初始化成员效果更好些。例如:

public class MyTest
{
 private TempObject _value;

 public MyTest()
 {
  try
  {
   _value = new TempObject();
  }
  catch( Exception err )
  {
    //Handle exception here
  }
 }
}

  对于成员初始化的好处以及其的使用限制,到此就已经说完了。不过,我个人更喜欢通过构造函数来初始化成员。对于文章中所说到的,使用构造函数来初始化成员的一些不利之处,例如:多个构造函数之间初始化操作的繁琐或者成员初始化的不统一,我觉得可以通过构造函数调用构造函数的方式来减少此类问题。不过这也只是我个人看法,至于具体用什么并没规定。

 

 
文章二:

C#作为一种纯面向对象的话言,为它编写的整个代码里面到处都离不开对象。一个对象的完整的生命周期是从开始分配空间到初始化,到使用,最后是销毁,使用的资源被回收。要想真正写出面高质量的代码,我们就得对这期间每一个阶段是怎么样一个状态,framework都做了些什么,我们又能够做些什么都要有些了解才行。
  一般来说大部分程序员对于一个创建好了的对象怎么使用都是比较清楚的,所以本文也就不想就这一部分做太多的说明,重点就集中开对象的创建和销毁这两个阶段,这也是程序员最容易范错误的阶断。本文首先来讲一讲对象成员的初始化,至于对象的释放和销毁,我想放到另外一篇文章里去讲。虽然本文是以C#2005 为例的,但推而广之,对于其它的基于CLS规范的语言应该也是一样的。

首先我们来看看引用类型的成员初始化过程

  我们来看一个例子吧 

class Program
{
    
static void Main(string[] args)
    {
         DriveB d 
= new DriveB();
     }
}

class BaseA
{
    
static DisplayClass a = new DisplayClass("基类静态成员初始化");

     DisplayClass BaseA_c 
= new DisplayClass("基类实例变量BaseA_c初始化");

    
public BaseA()
    {
         Console.WriteLine(
"基类构造方法被调用");
     }
}

class DriveB : BaseA
{
    
static DisplayClass DriveB_b = new DisplayClass("继承类静态成员DriveB_b初始化");

    
//static BaseA DriveB_a = new BaseA();

     DisplayClass DriveB_c 
= new DisplayClass("继承类实例变量DriveB_c初始化");

   
public DriveB()
    {
         Console.WriteLine(
"继承类构造方法被调用");
     }
}
class DisplayClass
{
    
public DisplayClass(string diplayString)
    {
         Console.WriteLine(diplayString);
         Console.WriteLine();
     }
}


程序动行的结果是:
继承类静态成员DriveB_b初始化
继承类实例变量DriveB_c初始化
基类静态成员初始化
基类实例变量BaseA_c初始化
基类构造方法被调用
继承类构造方法被调用

得出初始化顺序结论: 

1)继承类静态成员变量初始化 
2)继承类实例变量初始化 
3)基类静态静态成员变量初始化 
4)基类实例变量初始化 
5)基类构造方法调用 
6)继承类构造方法调用。 

  好像结果和JAVA的有点不一样啊, 有点混乱的感觉,搞不懂M$为什么要让初始化按这样的顺序执行,像JAVA那样严格的从基类到派生类多好呀.上例的运行结果说明, 构造函数这么这个和我们通常思路执行的顺序还是有一定的差别.对于实例成员初始化,基本上就是以下步骤执行:
1 类的对象初始化大体顺序上实例成员赋值到构造函数
2 成员赋值初始化按照由子类到父类的顺序
3 构造函数的初始化按照由父类到子类的顺序
从这里我们有一点需要注意的是,因为成员赋值初始化是从子类到父类的,所以在子类的成员赋值初始化的过程中,不要引用父类定义的成员,因为这个时候父类成员还没有开始初始化.需要说明一点的是C#在创建对象的第一步分配内存完成后会动把所有实例成员变量初始化成变量的默认值,例如整型就是0,引用类型就是null.然后才开始进行成员变量初始化的过程.C#并没有提供类似于C++构造函数中成员特殊的初始化方式:
public constructor(int a)i_a(a){}
估计是因为分配内存和初始化的严格分离,以及反射创建对象的需要,而且也不像C++那样追求的是extreme效率的原因吧;而且就像是以前看到有人说过,再好的语法级别的优化都不能改变写得烂的代码带来的效率低下.

  我们知道,C#里面的静态成员初始化不同于C++的静态成员初始化.C#里的静态成员只会在必要的时候,确切的说是在第一次访问该类的时候才会进行静态成员的初始化.这样做也是有一定道理的,一是减少了内存的开销,再就是加快了程序集启动的时间,很难想像多一个比较费时的静态初始化在程序启动的时候就一一进行,那样的等待会是比较痛苦的.而且大部分时间我们都只是使用一个程序集里面很少的一部分类,如果把程序集里面所有的类不管三七二十一都预先进行初始化的话,对内存和时间的浪废还是比较大的.

  了解了静态成员初始化的时机,就引出了另外一个问题,如果两个类相互间引用,比如A类的静态初始化里引用到了B类,B类的静态
初始化里又引用到了A类,这个时候又会出现什么样的结果呢,还是用例子还说明吧,请看下面这段代码:

using System;
 
class A
{
      
public static int X;
      
static A(){
         X
=B.Y+1;
      }
}
class B
{
      
public static int Y=A.X+1;
      
static B(){}
      
static void Main(){
              Console.WriteLine(
"X={0},Y={1}",A.X,B.Y);
      }
}


产生的输出结果是什么?

一般来说静态声明赋值语句先于静态构造函数执行,没有赋值的类成员声明会被初始化成该类型的默认值,也就是说
public static int X;
public static int Y=A.X+1;
比各自所在的静态构造函数先执行,前一句X没有赋值,默认就是0,后一句的Y在没有赋值之前也是0,赋值后就是A.X+1的值。
类的静态初始化包括成员变量的声明赋值,静态构造函数的执行。
静态初始化只有在类第一次被访问的时候才执行,而且是优先于第一次访问该类的代码执行

因为Main函数在class B中,所以程序先执行的是上面的第二条语句,声明一个Y,再给Y赋值
在赋值的时候又用到了A类中的X静态,当第一次访问A.X的时候,会先调用A类的静态构造函数,这里执行赋值X=B.Y+1,而重新去访问B类的成员,因为前面说的静态初始化只有第一次被访问的时候会执行,所以再次访问B类的时候不会重复进行静态初始化的。这时会因为前一次初始化还未完成,特别是B.Y还没有赋值完成,所以根据上面说的,B.Y现在处理只是声明完成的状态,所以现在B.Y的值就是0,相应的得到的X的值就是1了,在A类的静态构造函数执行完成的时候,程序会再回到B中Y的赋值语句上来,这时候得到的A.X的值就是1,而Y赋值完成后,此时值就变成了2了
因此最终输出的结果就是X=1,Y=2

  对于引用类型成员的初始化说了这么多还是总结一下吧.C#中初始化变量(包括实例成员变量和静态成员变量)可以采用成员声明的地方赋值的方式,也可以采用构造函数的方式.我个人在使用实例对象的时候比较推荐采用构造函数的方式,因为构造函数赋值的方式执行的顺序是从父类到子类,这种顺序避免了子类成员变量的初始化过程引用了未赋值的父类成员变量.而且在构造函数中初始化变量可以采用更多的语句块,更多的判断逻辑来初始化,甚至可以加上结构化异常处理try{}catch{}来处理异常信息,远比单单一个赋值语句来得灵活.不过对于简单的内置基本类型(如int,Enum,string等)就无所谓在哪里进行初始化了.

  以上是引用类型的初始化过程,值类型(这里主要是指的结构类型)的静态初始化和引用类型的完全一致.C#的结构类型是有构造函数的(记得C++里面结构也貌似可以声明构造函数),而实例成员的初始化因为结构没有派生的功能,所以在这方面反而比较简单.但是因为值类型始终是不能为空的,一旦声明就必须要分配相应的内存空间,有了内存空间当然是要首先进行初始化的了,这都是为了保证值类型的有效性吧.这个过程是由Framework来完成的,我们自己是没有办法写代码来控制.因此Framework自己在初始化调用构造函数的时候当然就需要对自己要调用的构造函数的参数作个统一的约定,最简单的就是无参构造函数了.所以在C#的每个结构里都默认隐含了一个无参的构造函数,程序员自己可以重载构造函数,但是不能声明自己的无参构造函数(这个是被Framework占用了的).

  有很多刚从C++转到C#的程序员在使用引用类型作为函数的临时变量的时候还能认识到在使用之前需要new一下创建实例再使用,但是在使用结构作为函数的临时变量的时候就喜欢声明后直接拿来使用,问起他们的时候总是说结构是值类型,值类型是存在栈上的,声明后就直接可以使用了.先不论这句话是不是正确的(关于C#中值类型和引用类型到底存在什么地方有时间以后一定写一篇文章专门讨论一下).首先按C#编程规范值类型同样是需要进行成员变量的封装的,很多值类型在声明后就不能够改变,而只声明一个结构体不赋值的话相当于是调用的默认的构造函数,而通常这个默认的构造函数对于我们来说是没有什么意义的.所以得到的值也是没有太大的用处,除非你是想用作out参数所实参,真正用到的时候还得另外赋值.所以当你这样使用结构体的时候,C#编译器会警告你,这个变量只是声明了没有赋值(其实是相当于有一个值,但是没有意义).其实变量使用之前赋值这也是一个很好的习惯,C++里面虽然直接声明了就可以用,但是一般也会在使用之前先ZeroMemory一下,这其实也是相当于初始化了结构体吧,唯一的区别是不需要重新分配空间.

<think>我们正在讨论C#成员初始化顺序以及如何使用for循环进行赋值。根据引用[4],C#初始化顺序涉及静态成员、实例成员以及构造函数(包括静态构造函数和实例构造函数)的调用顺序。特别要注意的是,静态成员首次被使用前初始化,实例成员则在创建对象时初始化。用户的问题包含两部分:1.成员初始化顺序2.使用for循环对成员进行赋值的示例我们将分两部分回答:第一部分:成员初始化顺序(简要概述)第二部分:提供一个使用for循环对成员(例如数组)进行赋值的示例注意:成员初始化顺序通常由它们在中声明的顺序决定,但实际初始化发生在构造函数执行之前(对于实例成员)或静态构造函数执行之前(对于静态成员)。然而,在构造函数中,我们可以使用循环来对成员(如数组)进行赋值。示例说明:假设我们有一个,其中包含一个整型数组成员,我们想在构造函数中使用for循环来初始化这个数组。根据引用[1]和[3],数组可以在声明时初始化,也可以在构造函数中初始化。这里我们将展示在构造函数中使用for循环进行初始化。另外,引用[4]提到实例构造函数的顺序:先初始化(如果有),然后初始化当前的实例成员(按照声明顺序),最后执行当前的构造函数体。因此,在构造函数体内,我们可以安全地使用循环来为数组赋值,因为此时数组已经被分配了内存(通过new操作符)。我们创建一个名为MyClass,其中包含一个int数组,并在构造函数中使用for循环给数组的每个元素赋值。代码示例:```publicclassMyClass{//声明一个数组,但不在声明时初始化(或者也可以先初始化为null,然后在构造函数中初始化)publicint[]MyArray;//构造函数,参数n表示数组大小publicMyClass(intn){//初始化数组,分配内存MyArray=newint[n];//使用for循环为数组的每个元素赋值for(inti=0;i<n;i++){MyArray[i]=i*10;//示例赋值,可以根据需要修改}}}```使用方式:```classProgram{staticvoidMain(string[]args){//创建MyClass的实例,数组大小为5MyClassobj=newMyClass(5);//输出数组内容for(inti=0;i<obj.MyArray.Length;i++){Console.WriteLine($"MyArray[{i}]={obj.MyArray[i]}");}}}```输出:MyArray[0]=0MyArray[1]=10MyArray[2]=20MyArray[3]=30MyArray[4]=40注意:在构造函数中,我们首先通过`newint[n]`为数组分配内存,然后使用for循环进行赋值。这样确保数组在对象创建时被正确初始化。另外,如果中有多个数组或集合需要初始化,也可以使用似的方法。引用[3]中提到了列表的初始化,我们同样可以在构造函数中使用循环来添加元素。例如,使用List<int>:```publicclassMyClassWithList{publicList<int>MyList;publicMyClassWithList(intn){MyList=newList<int>();for(inti=0;i<n;i++){MyList.Add(i*10);}}}```总结:1.成员初始化顺序:静态成员->基构造函数->实例成员(按照声明顺序初始化)->当前构造函数体。2.在构造函数体内,我们可以使用循环对成员(如数组或列表)进行赋值。相关问题:1.在C#中,如果成员是数组,除了在构造函数中初始化,还有哪些初始化方式?2.如何确保在多级继承中,基和派生的数组成员都能正确初始化?3.在C#中,静态数组的初始化有什么特殊要求?如何使用循环初始化静态数组?注意:静态数组的初始化必须在静态构造函数中或声明时进行,因为静态成员被首次使用前初始化,且不能通过实例构造函数初始化(因为静态成员属于,不属于实例)。参考文献:[^1]:数组的声明和初始化示例。[^3]:中列表初始化的示例。[^4]:初始化顺序的说明。</think>### C# 成员初始化顺序与 for 循环赋值示例 在 C# 中,成员初始化顺序遵循严格规则[^4]: 1. **静态成员初始化**:静态字段和静态构造函数最先执行 2. **基初始化**:若有继承关系,先执行基构造函数 3. **实例成员初始化**:按声明顺序初始化实例字段 4. **构造函数体执行**:最后执行当前的构造函数体 #### for 循环赋值的成员示例 ```csharp public class MatrixProcessor { // 1. 静态成员初始化(最先执行) private static readonly int MaxSize = 100; // 2. 实例成员初始化(按声明顺序) private int[,] _matrix; private int[] _rowSums; private int _totalSum; // 构造函数(最后执行) public MatrixProcessor(int rows, int cols) { // 使用 for 循环初始化二维数组 _matrix = new int[rows, cols]; for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { _matrix[i, j] = (i * cols) + j; // 按行列索引赋值 } } // 使用 for 循环计算行和 _rowSums = new int[rows]; for (int i = 0; i < rows; i++) { int sum = 0; for (int j = 0; j < cols; j++) { sum += _matrix[i, j]; } _rowSums[i] = sum; _totalSum += sum; // 累加总和 } } public void PrintResults() { Console.WriteLine($"总行数: {_rowSums.Length}"); for (int i = 0; i < _rowSums.Length; i++) { Console.WriteLine($"行 {i} 总和: {_rowSums[i]}"); } Console.WriteLine($"矩阵总和: {_totalSum}"); } } // 使用示例 class Program { static void Main() { var processor = new MatrixProcessor(3, 4); processor.PrintResults(); } } ``` #### 初始化顺序验证: ```csharp public class InitializationOrderDemo : BaseClass { // 静态字段(最先初始化) private static string _staticField = "静态字段".PadRight(10); // 实例字段(按声明顺序初始化) private string _field1 = "字段1".PadRight(10); private string _field2 = "字段2".PadRight(10); public InitializationOrderDemo() { Console.WriteLine("构造函数执行"); Console.WriteLine($"{_staticField} | {_field1} | {_field2}"); } } public class BaseClass { public BaseClass() => Console.WriteLine("基构造函数"); } /* 输出顺序: 基构造函数 字段1初始化 字段2初始化 构造函数执行 静态字段 | 字段1 | 字段2 */ ``` #### 关键要点: 1. **for 循环最佳位置**:应在构造函数中进行复杂初始化(如嵌套循环),确保成员已分配内存[^1][^3] 2. **数组初始化**:必须先用 `new` 分配内存才能使用循环赋值[^1] 3. **成员访问顺序**: - 静态字段可在声明时直接初始化 - 实例字段可在声明时简单初始化,复杂逻辑需在构造函数中完成 4. **继承场景**:派生的循环初始化应在基初始化后执行[^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值