静态构造函数如何工作?


82
namespace MyNameSpace
{
    static class MyClass
    {
        static MyClass()
        {
            //Authentication process.. User needs to enter password
        }

        public static void MyMethod()
        {
            //Depends on successful completion of constructor
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MyClass.MyMethod();
        }
    }
}

这是我假设的顺序

  1. 静态构造函数的开始
  2. 静态构造函数的结尾
  3. 主开始
  4. MyMethod的开始
  5. 主端

现在,在任何情况下,如果4将在2之前启动,我都会被搞砸。可能吗?


8
这是Java还是C#问题?您已经放置了两个标签,并且我认为两种语言的规范并不相同。
ARRG 2012年

在我看来,这对双方来说都是一样的。.但是我是C#家伙。.对此表示
抱歉

4
Java没有相同的静态构造函数,只有用于静态初始化的静态块。静态{//做某事...}
deraj 2012年

2
就个人而言,我对静态构造函数内部的任何形式的交互感到不舒服。我了解您的目标(让此静态类中的每个方法都等待用户授权后再运行),但确实不喜欢这种方法来完成它。
Brian

@Brian:-是的...您是对的...我只是在做分析..最后我只是决定不使用构造函数,而是使用Initialize方法
om471987 2012年

Answers:


220

您在这里只问了一个问题,但是应该回答十二个左右的问题,所以我会全部回答。

这是我假设的顺序

  1. 类构造函数的开始(也称为cctor
  2. cctor结束
  3. 主开始
  4. MyMethod的开始

它是否正确?

否。正确的顺序是:

  1. 如果有的话,启动cctor for Program。那没有。
  2. 程序的cctor结束(如果有)。那没有。
  3. 主开始
  4. cctor for MyClass的开始
  5. cctor for MyClass结束
  6. MyClass.MyMethod的开始

如果有一个静态字段初始化器怎么办?

在某些情况下,允许CLR更改静态字段初始化程序运行的顺序。有关详细信息,请参见Jon关于该主题的页面:

静态构造函数和类型初始值设定项之间的区别

是否有可能MyMethod在该类的cctor完成之前调用静态方法?

是。如果cctor本身调用MyMethod,则显然在cctor完成之前将调用MyMethod。

cctor不调用MyMethod。是否有可能MyMethod在MyClass的cctor完成之前调用静态方法?

是。如果cctor使用另一种类型,其cctor调用MyMethod,则将在MyClass cctor完成之前调用MyMethod。

没有cctor可以直接或间接调用MyMethod!现在是否有可能MyMethod在MyClass的cctor完成之前调用静态方法?

没有。

即使涉及多个线程,这仍然是正确的吗?

是。在可以在任何线程上调用static方法之前,cctor将在一个线程上完成。

cctor可以多次调用吗?假设两个线程都导致cctor运行。

保证cctor最多调用一次,无论涉及多少线程。如果两个线程“同时”调用MyMethod,则它们竞争。其中一个输掉比赛并阻塞,直到MyClass cctor在获胜线程中完成。

丢失的线程会阻塞,直到cctor完成?真的

真。

那么,如果胜利线程上的cctor调用了代码,该代码阻塞了失败线程先前所获取的锁?

然后,您有一个经典的锁定顺序反转条件。您的程序陷入僵局。永远。

那似乎很危险。如何避免僵局?

如果这样做时很痛,那就停止这样做永远不要做会阻碍cctor的事情。

依靠cctor初始化语义来强制执行复杂的安全性要求是一个好主意吗?拥有负责用户交互的cctor是个好主意吗?

都不是好主意。我的建议是,您应该找到一种不同的方法来确保满足您的方法中影响安全性的前提条件。


5
埃里克(Eric),我很好奇为什么在此答案中将“静态构造函数”替换为“类构造函数”或“ cctor”。在引用cctor时使用“静态构造函数”是否不合适?
phoog 2012年

6
@phoog:我想保持术语使用的一致性,所以我选择了最短的术语。“静态构造函数”和“类构造函数”都可以。作为实现细节,类型的静态构造函数是作为称为“ .cctor”的特殊方法发出的,因此通常将此类构造函数称为“ cctor”。如果在更正式的语境中写作,我会使用较长的术语之一。
埃里克·利珀特

@EricLippert这对于具有静态构造函数的非静态类也适用吗?
传奇

2
@Legends:对于具有静态构造函数的非静态类也是如此吗?是。
埃里克·利珀特

24

根据MSDN,静态构造函数:

在创建第一个实例或引用任何静态成员之前,将自动调用静态构造函数来初始化类。

因此,静态构造函数将在调用静态方法之前MyClass.MyMethod()被调用(假设当然在静态构造或静态字段初始化期间不会调用)。

现在,如果您要在其中进行任何异步操作static constructor,那么同步它是您的工作。


7
如果您要执行任何涉及静态构造函数中第二个线程的异步操作,那么您将痛苦不堪。没有什么可以使僵局更快。有关示例,请参见stackoverflow.com/a/8883117/88656
埃里克·利珀特

@Eric:同意...我不想这样做,但是不确定从他的榜样到MyMethod被调用时他到底想要完成什么……
James Michael Hare

11

#3实际上是#1:静态初始化直到第一次使用它所属的类时才开始。

可以MyMethod从静态构造函数或静态初始化块中调用。如果您不MyMethod直接或间接从静态构造函数调用,则应该没问题。


请注意,根据我的理解,static初始化可以在首次使用之前实际调用,具体取决于优化的资格。
詹姆斯·迈克尔·黑尔


1
对于静态构造函数,是正确的,但对于静态初始化是我的观点。抱歉,也许我只是在说“静态初始化无法启动...”这句话对静态构造是正确的,但是如果类没有静态构造函数,则不是这样,那么静态初始化可能在之前发生。
詹姆斯·迈克尔·黑尔

抱歉,我可能只是在对单词进行过多分析。在这个问题的上下文中它是绝对正确的,我只是担心这句话是在没有显式静态构造函数的类的上下文中作为静态初始化的独立语句。
詹姆斯·迈克尔·黑尔

@James:您并不过分分析-术语是这里的关键区别。静态构造函数是C#概念,而类型初始化是.NET。静态构造函数(C#)中的代码成为类型初始化程序(.NET)的一部分,但是类型初始化程序触发的时间方式(即beforefieldinit语义)取决于C#类是否具有静态构造函数。
路加

9

文档(重点是我的):

创建第一个实例或引用任何静态成员之前,将自动调用静态构造函数来初始化类 。


2

您可以保证4总是在2之后(如果您不是通过静态方法创建类的实例),但是1和3并非如此。


2

在执行mymethod之前,将调用静态构造函数。但是,如果在2之前调用4时感到困惑,那么我建议您重新考虑设计。无论如何,不​​应该在静态构造函数中做复杂的事情。


2

CLR保证静态构造函数在访问任何静态成员之前运行。但是,您的设计有点臭。做这样的事情会更直接:

static void Main(string[] args) 
{ 
     bool userIsAuthenticated = MyClass.AuthenticateUser();
     if (userIsAuthenticated)
         MyClass.MyMethod(); 
 } 

对于您的设计,如果身份验证失败,则阻止MyMethod运行的唯一方法是引发异常。


2

确保在执行任何静态方法之前已调用静态类的构造函数。例:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Press enter");
        Console.ReadLine();
        Boop.SayHi();
        Boop.SayHi();
        Console.ReadLine();
    }

}

static class Boop
{
    static Boop()
    {
        Console.WriteLine("Hi incoming ...");
    }

    public static void SayHi()
    {
        Console.WriteLine("Hi there!");
    }
}

输出:

按回车

//在按回车键之后

嗨,来...

嗨,您好!

嗨,您好!


使用系统;名称空间MyNameSpace {类Program {静态void Main(string [] args){Console.WriteLine(“ Entered in main”); Boop.SayHi(); Boop.SayHi(); }}静态类Boop {static Boop(){Console.Read(); Console.WriteLine(“已输入构造键”); } public static void SayHi(){Console.WriteLine(“方法被称为”); }}}是的,该程序可提供更好的理解
om471987 2012年

可能吧 不过,下一次将其发布为答案。然后,它会更加有用和可见。
haiyyu 2012年

1

这是发生故障的实际顺序:

  1. 开始于 Main
  2. 静态MyClass构造函数的开始
  3. 静态MyClass构造函数的结尾
  4. 开始于 MyMethod
  5. 结束 Main

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.