首页 > 解决方案 > 为什么不调用这个 C# 实例构造函数,除非有对非静态成员的引用?

问题描述

此代码似乎没有调用Mixed构造函数并打印y = 0

public class Mixed
{
    public int x;
    public static int y;

    public Mixed()
    {
        x = 1;
        y = 1;
    }
}    

public class Program
{
    static Mixed mixed = new Mixed();

    static void Main(string[] args)
    {
        Console.WriteLine("y = " + Mixed.y);

        Console.ReadLine();
    }
}

但是,简单地修改Main函数使其看起来像这样会导致调用构造函数。

static void Main(string[] args)
{   
    Console.WriteLine("x = " + mixed.x);
    Console.WriteLine("y = " + Mixed.y);

    Console.ReadLine();
}

这打印:

x = 1
y = 1

为什么简单地将此引用添加到非静态字段会导致构造函数被正确调用?不应该创建对象总是导致调用构造函数,而不管该对象稍后在程序中如何使用?

奇怪的是,Mixed像这样使对象非静态也会导致构造函数被调用:

public class Program
{
    static void Main(string[] args)
    {
        Mixed mixed = new Mixed();

        Console.WriteLine("y = " + Mixed.y);

        Console.ReadLine();
    }
}

但是,这对我来说似乎也没有意义。将对象声明Mixed为静态应该只意味着内存中只有一个对象的副本,无论 Program 被实例化多少次。这是某种编译器优化,对于静态字段,编译器在实际实例化之前等待对该类型的非静态字段的引用?

标签: c#static.net-core

解决方案


您遇到的是Program类型的静态字段没有被初始化。

你会发现一个类型的所有静态字段在第一次被访问时都会被初始化。这是在不同的运行时(.NET Framework、.NET Core、Mono)之间达成一致的。

如以下 IL 代码所示,您的示例将生成一个.cctor(静态构造函数)来初始化字段。

.class public auto ansi beforefieldinit ConsoleApp1.Program
    extends [System.Runtime]System.Object
{
    .field private static class ConsoleApp1.Mixed mixed

    // (...) omitted irrelevant methods

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

        IL_0000: newobj instance void ConsoleApp1.Mixed::.ctor()
        IL_0005: stsfld class ConsoleApp1.Mixed ConsoleApp1.Program::mixed
        IL_000a: ret
    }

}

然而,在什么时候初始化这些字段并没有达成一致。例如,.NET Core 将延迟加载字段,就在它们被访问之前。当访问该类型的任何成员(包括方法)时,.NET Framework 将急切地加载字段。

以下代码在 .NET Core 和 .NET Framework 中会产生不同的结果。

public class Program
{
    static Mixed mixed = new Mixed();

    static string text = "Hello, World!";

    static void Main(string[] args)
    {
        Console.WriteLine("y = " + Mixed.y);
        Console.WriteLine(text);
        Console.WriteLine("y = " + Mixed.y);

        Console.ReadLine();
    }
}

在 .NET 核心中:

y = 0
你好,世界!
y = 1

在 .NET 框架中:

y = 1
你好,世界!
y = 1


如果向类添加静态构造函数,则在访问任何字段或方法时始终会初始化字段:

// ... adding to previous Program class
static Program()
{
    // empty body
}

在 .NET 核心中:

y = 1
你好,世界!
y = 1

发生这种情况是因为beforefieldinit标志不会被添加到具有自定义静态构造函数的类型中。此标志表示该类型的字段可以尽可能延迟/延迟加载。

当省略此标志时,字段将被急切地初始化。当标志存在时,字段至少会延迟加载,但也可以急切地加载,这取决于运行时。

如前所述,当标志存在时,.NET Core 将延迟加载字段。.NET Framework 将急切地加载字段,无论标志是否存在。


推荐阅读