Skip to content

C# 关于 Class 的构造函数及字段赋初值

🏷️ C#

新建一个空的 Class:

csharp
namespace CtorSample
{
    class SomeType
    {
    }
}

编译后生成的 IL 代码:

cs
.class private auto ansi beforefieldinit CtorSample.SomeType
       extends [mscorlib]System.Object
{
  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    // 代码大小       7 (0x7)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0006:  ret
  } // end of method SomeType::.ctor

} // end of class CtorSample.SomeType

ctor 即构造函数,虽然 SomeType 中没有写任何代码,但是编译器还是自动生成了默认的ctor方法。

代码中调用了基类 Object 的构造函数方法。其代码等同于:

csharp
namespace CtorSample
{
    class SomeType
    {
        public SomeType()
            : base()
        {
        }
    }
}

对应的 IL 代码:

cs
.class private auto ansi beforefieldinit CtorSample.SomeType
       extends [mscorlib]System.Object
{
  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    // 代码大小       10 (0xa)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0006:  nop
    IL_0007:  nop
    IL_0008:  nop
    IL_0009:  ret
  } // end of method SomeType::.ctor

} // end of class CtorSample.SomeType

可以看出只是多了几个 nop 空操作,其它处理都是一样的。

再增加两个实例字段,一个赋了初始值,一个在构造函数中赋初值。看一下编译器是怎么处理的。

csharp
namespace CtorSample
{
    class SomeType
    {
        public int firstProperty = 5;

        public int secondProperty;

        public SomeType()
            : base()
        {
            secondProperty = 10;
        }
    }
}

对应的 IL 代码:

cs
.class private auto ansi beforefieldinit CtorSample.SomeType
       extends [mscorlib]System.Object
{
  .field public int32 firstProperty
  .field public int32 secondProperty
  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    // 代码大小       25 (0x19)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  ldc.i4.5
    IL_0002:  stfld      int32 CtorSample.SomeType::firstProperty
    IL_0007:  ldarg.0
    IL_0008:  call       instance void [mscorlib]System.Object::.ctor()
    IL_000d:  nop
    IL_000e:  nop
    IL_000f:  ldarg.0
    IL_0010:  ldc.i4.s   10
    IL_0012:  stfld      int32 CtorSample.SomeType::secondProperty
    IL_0017:  nop
    IL_0018:  ret
  } // end of method SomeType::.ctor

} // end of class CtorSample.SomeType

看到这里有些意外,没想到写在字段上的初值是在构造函数里设置的。具体的赋值及调用的顺序是这样的:

  1. firstProperty = 5

  2. call System.Object.ctor()

  3. secondProperty = 10

再加一个带参数的构造函数:

csharp
namespace CtorSample
{
    class SomeType
    {
        public int firstProperty = 5;

        public int secondProperty;

        public SomeType()
            : base()
        {
            secondProperty = 10;
        }

        public SomeType(int firstProperty, int secondProperty)
        {
            this.firstProperty = firstProperty;
            this.secondProperty = secondProperty;
        }
    }
}

对应的 IL 代码:

cs
.class private auto ansi beforefieldinit CtorSample.SomeType
       extends [mscorlib]System.Object
{
  .field public int32 firstProperty
  .field public int32 secondProperty
  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    // 代码大小       25 (0x19)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  ldc.i4.5
    IL_0002:  stfld      int32 CtorSample.SomeType::firstProperty
    IL_0007:  ldarg.0
    IL_0008:  call       instance void [mscorlib]System.Object::.ctor()
    IL_000d:  nop
    IL_000e:  nop
    IL_000f:  ldarg.0
    IL_0010:  ldc.i4.s   10
    IL_0012:  stfld      int32 CtorSample.SomeType::secondProperty
    IL_0017:  nop
    IL_0018:  ret
  } // end of method SomeType::.ctor

  .method public hidebysig specialname rtspecialname 
          instance void  .ctor(int32 firstProperty,
                               int32 secondProperty) cil managed
  {
    // 代码大小       31 (0x1f)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  ldc.i4.5
    IL_0002:  stfld      int32 CtorSample.SomeType::firstProperty
    IL_0007:  ldarg.0
    IL_0008:  call       instance void [mscorlib]System.Object::.ctor()
    IL_000d:  nop
    IL_000e:  nop
    IL_000f:  ldarg.0
    IL_0010:  ldarg.1
    IL_0011:  stfld      int32 CtorSample.SomeType::firstProperty
    IL_0016:  ldarg.0
    IL_0017:  ldarg.2
    IL_0018:  stfld      int32 CtorSample.SomeType::secondProperty
    IL_001d:  nop
    IL_001e:  ret
  } // end of method SomeType::.ctor

} // end of class CtorSample.SomeType

可以看到 IL_0000~IL_0008 的代码是一样的,也就是说字段上的赋初值及调用基类的构造函数处理在每个构造函数上都生成了一遍,即同样的处理生成了两遍 IL 代码。

那么如何避免这种重复代码出现以减小编译文件的大小呢?建议将初始值的设定都放在一个共通的构造函数中,其它的构造函数再调用这个构造函数。

修改后代码如下:

csharp
namespace CtorSample
{
    class SomeType
    {
        public int firstProperty;
        public int secondProperty;

        public SomeType()
            : base()
        {
            firstProperty = 5;
            secondProperty = 10;
        }

        public SomeType(int firstProperty, int secondProperty) : this()
        {
            this.firstProperty = firstProperty;
            this.secondProperty = secondProperty;
        }
    }
}

对应的 IL 代码:

cs
.class private auto ansi beforefieldinit CtorSample.SomeType
       extends [mscorlib]System.Object
{
  .field public int32 firstProperty
  .field public int32 secondProperty
  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    // 代码大小       25 (0x19)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0006:  nop
    IL_0007:  nop
    IL_0008:  ldarg.0
    IL_0009:  ldc.i4.5
    IL_000a:  stfld      int32 CtorSample.SomeType::firstProperty
    IL_000f:  ldarg.0
    IL_0010:  ldc.i4.s   10
    IL_0012:  stfld      int32 CtorSample.SomeType::secondProperty
    IL_0017:  nop
    IL_0018:  ret
  } // end of method SomeType::.ctor

  .method public hidebysig specialname rtspecialname 
          instance void  .ctor(int32 firstProperty,
                               int32 secondProperty) cil managed
  {
    // 代码大小       24 (0x18)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  call       instance void CtorSample.SomeType::.ctor()
    IL_0006:  nop
    IL_0007:  nop
    IL_0008:  ldarg.0
    IL_0009:  ldarg.1
    IL_000a:  stfld      int32 CtorSample.SomeType::firstProperty
    IL_000f:  ldarg.0
    IL_0010:  ldarg.2
    IL_0011:  stfld      int32 CtorSample.SomeType::secondProperty
    IL_0016:  nop
    IL_0017:  ret
  } // end of method SomeType::.ctor

} // end of class CtorSample.SomeType

可以看到在第二个构造函数中先调用了第一个构造函数,然后再执行自身的代码。如果 class 有很多字段需要初始化的话,这样可以显著的减少 IL 代码的行数。