为什么我们需要静态构造函数?

9
首先,我在《什么是静态构造函数?》中得到了一个答案,但我希望在这个背景下得到一个答案。
以下是我的C#静态类:
public static class BasicClass
{
    static int i = 0;
    static BasicClass()
    {
        i = 10;
    }

    public static void Temp()
    {
        //some code
    }


    public static void Temp1()
    {
        //some code
    }
}

在这个函数里,我有一个静态变量i,当它被首次调用时,它的值被初始化为10。所以基本上它可能是一个静态构造函数的目的,但是通过初始化static int i=10而不是声明静态构造函数也可以实现同样的目的,即只被初始化一次。
那么我们为什么需要静态构造函数呢?或者说我完全错误地理解了静态构造函数的概念或用途吗?

2
如果你的程序使用配置文件,你会想把配置存储在静态变量中,但首先你需要从文件中读取这些变量。如果文件不存在(或由于其他原因无法打开),你需要将这些配置设置为默认值。这就是静态构造函数可以用来做的事情。 - Nolonar
8个回答

24
如果您将该类编译为程序集,然后使用ILSpy或类似工具反汇编结果,您将注意到所有静态成员初始化都在静态构造函数中执行。
例如,以下C#代码:
public static class BasicClass
{
    static int i = 10;
}

将产生等效于IL的代码:

public static class BasicClass
{
    static int i;

    static BasicClass()
    {
        i = 10;
    }
}

换句话说,直接初始化只是C#编译器提供的语法糖。在底层,仍然实现了静态构造函数。

那么,如果C#支持静态成员初始化(实际上会编译为静态构造函数),而不是实际的静态构造函数,这样就足够了吗? - svick
1
@svick,那可能会适得其反。有时您确实需要为类全局执行比成员初始化(例如简单的循环和分支)更不平凡的操作。为什么要删除一个在CLR中可用且只需要静态构造函数从不抛出异常的功能呢? - Frédéric Hamidi

6

嗯,在你的例子中确实不需要,但是想象一下当i的值必须从数据库、文本文件或任何其他资源中读取时会发生什么?您可能需要像这样的内容:

static BasicClass()
{
    using (SomeConnection con = Provider.OpenConnection())
    {
        try
        {
            // Some code here
        }
        catch
        {
            // Handling expeptions, setting default value
            i = 10;
        }
    }
}

现在不能声明并初始化静态字段,最好使用静态构造函数。


1
答案也在您链接的问题中:
“[...] 特别适用于将必需的配置数据读入只读字段等。第一次需要时它会自动运行(确切规则很复杂(请参见“beforefieldinit”),并且在CLR2和CLR4之间略有不同)。除非您滥用反射,否则保证最多运行一次(即使两个线程同时到达)。 ”
您可以在静态构造函数中初始化更复杂的内容,例如设置数据库连接等。是否有意义是另一回事...

1
在这种情况下,您不需要静态构造函数。
static BasicClass()
{
    i = 10;
}

并且

static int i = 10;

功能上是相同的。


1
一个静态构造函数有意义,如果你需要在构造函数内部执行一些操作,并且你想要应用程序中的唯一实例。例如:
public static class BasicClass
{
    static MyConfiguration _myConfig;

    static BasicClass()
    {
       // read configuration from file
       _myConfig = ReadConfigFromConfigFile("somefile.conf");
    }

    private static MyConfiguration ReadConfigFromConfigFile(string file)
    {
       using (StreamReader reader = new StreamReader(file);
       {
         ...
       }
    }
}

在您所解释的情况下,您不需要显式地使用静态构造函数。
此外,您可以应用单例模式来实现这个目的。

5
在静态构造函数中做这样的事情是危险的。如果初始化引发了未被捕获的异常,则没有简单的方法可以捕获它,那么该类型将永远无法加载到该应用程序域中。 - Eric Lippert

0

你只能使用编译时常量(在你的情况下)来初始化字段。但是在静态构造函数中,你可以执行一些代码(例如读取配置文件)。


0
一个静态构造函数不仅初始化变量,还会创建对象并调用这些对象或类本身的方法。这对于你的(静态)类或环境工作可能是必要的。

0

还有一个尚未提及的因素是,使用构造函数语法编写静态构造函数调用foo()与使用静态字段初始化程序调用foo()之间存在语义差异。特别地,如果一个类型具有使用构造函数语法编写的静态构造函数,则保证在执行“使用”该类型的代码的第一次执行时调用该构造函数;在此之前不会调用它,也不会在不使用该类型时调用它。相比之下,虽然.NET保证类型的静态字段初始化程序将在访问其任何静态字段之前运行,并且不会运行多次,但.NET可以自由地在它认为可能会使用类型的第一次运行这样的静态初始化程序。例如:

if (someCondition())
  for (i=0; i<100000000; i++)
    someClass.someStaticField++;

如果当即时编译器遇到上述代码时,someClass从未被使用过,并且它有一个使用静态构造函数语法声明的构造函数,那么生成的机器代码将类似于以下内容:

if (someCondition())
  for (i=0; i<100000000; i++)
  {
    if (someClass.hasBeenInitialized)
      someClass.runConstructor();
    someClass.someStaticField++;
  }

在循环内执行if检查。相比之下,如果有字段初始化但没有构造函数式声明,JIT可能会在运行代码之前执行someClass的静态构造,从而消除了在其中包含if检查的需要。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接