C#构造函数

引用类型的实例构造函数不能被继承(不过,可以通过 base 关键字调用父类的构造函数),它负责初始化类型的实例字段。

对于静态字段,由静态构造函数负责。当然,也可以在实例构造函数中为静态成员赋值,但是,当之后更改这个值,再创建一个新的实例时,你会发现值又被实例构造函数改回去了。

所以,通常在静态构造函数中为静态成员赋值 (因为它只会执行一次)。

如果类型没有定义任何构造函数,C# 会生成一个无参实例构造函数(.ctor),它遍历类 型中所有的成员,并将它们设置为默认值。

可以声明多个不同的构造函数。可以利用 this 关键字来调用其他构造函数。例如:

public class AClass
{
    public int i;
    public static int j;
    static AClass()
    {
        j = 2;
        Console.WriteLine("静态构造函数");
    }
    public AClass() : this(5)
    {
        Console.WriteLine("实例构造函数");
    }
    public AClass(int i)
    {
        this.i = i;
        Console.WriteLine("有参数的实例构造函数");
    }
}

在这个例子中,我们使用下面的代码新建实例:

static void Main(string[] args)
{
    var a = new AClass();
    Console.WriteLine(a.i);

    var b = new AClass(10);
    Console.WriteLine(b.i);
    Console.ReadKey();
}

根据规则,创建 a 时,调用无参的构造函数。但无参的构造函数调用了有参的构造函数,所以,先执行有参的构造函数,将 i 设置为 5,再执行无参的构造函数。

创建 b 则只调用有参的构造函数。最后的输出结果如下图所示。

C#构造函数

实例构造函数(值类型)

如果没有为结构定义构造函数,则 CLR 会生成一个默认的,将其成员初始化为默认值。

如果你自定义一个结构体的构造函数,那么必须初始化它的所有成员。结构的自定义构造函数不会被自动调用,所以你要手动地通过 this 来调用。

不能显式地为结构声明无参数的构造函数。

静态构造函数

静态构造函数又称类型构造函数,是一个特殊的构造函数,它会在这个类型第一次被实例化或引用任何静态成员之前执行,它具有以下特点:

  • 静态构造函数既没有访问修饰符,也没有参数。
  • 在创建第一个实例或引用任何静态成员之前,将自动调用静态构造函数来初始化类(的类型对象)。静态构造函数只会执行一次。
  • 无法直接调用静态构造函数。它的访问修饰符是 private(不需要写明)。在程序中,用户无法控制何时执行静态构造函数。
  • 静态构造函数不应该调用基类型的静态构造函数。这是因为类型不可能有从基类型继承的静态字段。
  • 只有在类型包括静态字段且在定义时有赋值动作,C# 才会自动生成一个静态构造函数。

静态构造函数的目的是为了安全地给静态成员赋值。你可以显式定义静态构造函数为静态成员赋值。

如果什么都不做,它通过元数据得知这个类型有什么静态成员,并在其中初始化这些静态成员为默认值。

静态构造函数默认是没有的。因为它的功能是确定的(为静态字段赋值),而且只运行一次。

所以,它不需要访问修饰符和参数。如果没有显式定义,而且,没有在代码中为静态成员赋值,则 C# 不会自动生成静态构造函数。

例如,为静态成员赋值会导致 IL 岀现 .cctor:

class ExampleRef
{
private int a;
private string b;
private static string c = "static";

这会令 C# 在 .cctor 中初始化静态成员:

.method private hidebysig specialname rtspecialname static void .cctor () cil managed
{
    // Method begins at RVA 0x208f
    // Code size 11 (0xb)
    .maxstack 8

    IL_0000: Idstr "static"
    IL_0005: stsfld string TypeFundamentalLab.ExampleRef::c
    IL_000a: ret
} // end of method ExampleRef::.cctor

如果去掉赋值语句:c = "static",则不会生成静态构造函数。如果一个类型没有静态字段,那么更不需要自动生成静态构造函数了。

当创建第一个实例之前,堆上没有类型对象,所以要调用静态构造函数,引用静态成员之前,堆上也没有类型对象,而静态成员属于类型对象的一部分,所以也要调用静态构造函数。

这两种情况的最终结果,都是堆上最终出现了一个类型对象。因为类型对象只需要建立一次,所以这个静态构造函数也只能运行一次。在这之后,所有的实例对象都指向这个类型对象。

由于 C# 设计成不让外界直接访问静态构造函数,为静态构造函数设置访问修饰符(例如,设置为 public)或传入参数都是没有意义的,因为不可以主动调用。

静态构造函数只负责初始化静态成员,为类型对象的创建而服务,它和类型的实例对象没有关系。

构造函数的执行顺序

如果存在一个父类和继承它的子类,那么:

  • 执行静态构造函数(先子类后父类)。在这一步中,如果发现父类或子类的静态构造函数已经执行过,那么就不会再次执行
  • 执行实例构造函数(先父类后子类)。在这一步中,如果遭遇 this,则先调用 this 后面指向的实例构造函数的重载,然后再执行自己。

如果父类的实例构造函数是私有的,那么子类构造函数无法通过编译:

class Father
{
    public static int i;
    static Father()
    {
        i = 1;
        Console.WriteLine("父类型的静态构造函数");
    }
    Father()
    {
        Console.WriteLine("父类型的实例构造函数");
    }
}
class Son : Father
{
    public static int j;
    static Son()
    {
        j = 1;
        Console.WriteLine("子类型的静态构造函数");
    }
    Son()
    {
        Console.WriteLine("子类型的实例构造函数");
    }
}

上面的代码中父类 Father 的实例构造函数没有访问修饰符,因此,默认会使用 private。

子类的实例构造函数便无法访问父类的实例构造函数,不能通过编译,如下图所示。

父类实例构造函数为私有导致子类无法完成编译

即使去掉显式定义的 Son 方法,结果也是一样。因此,这可以证明子类的实例构造函数确实会调用父类的实例构造函数。

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/22367.html

(0)
上一篇 2021年7月20日
下一篇 2021年7月20日

相关推荐

发表回复

登录后才能评论