所以我很高兴地从Eric Lippert那里阅读this然后,当然,优秀的评论和其中John Payson说:
一个更有趣的例子可能是使用两个静态类,因为这样的程序可能会死锁而没有任何可见的阻塞语句。
我想,是的,那很容易,所以我把它搞砸了:
public static class A
{
static A()
{
Console.WriteLine("A.ctor");
B.Initialize();
Console.WriteLine("A.ctor.end");
}
public static void Initialize()
{
Console.WriteLine("A.Initialize");
}
}
public static class B
{
static B()
{
Console.WriteLine("B.ctor");
A.Initialize();
Console.WriteLine("B.ctor.end");
}
public static void Initialize()
{
Console.WriteLine("B.Initialize");
}
public static void Go()
{
Console.WriteLine("Go");
}
}
其输出(在调用B.Go()
之后)是:
B.ctor
A.ctor
B.Initialize
A.ctor.end
A.Initialize
B.ctor.end
Go
没有僵局,我显然是一个失败者 - 所以为了使这种尴尬永久化,这是我的问题:为什么这里没有僵局?
在B.Initialize
的静态构造函数完成之前,B
在之前被{{1}}称为,我认为这是不允许的。
答案 0 :(得分:6)
这不是一个僵局,因为你没有做任何应该阻止的事情,也没有做任何应该破坏的事情。
您未在A
内使用B
中的任何资源,反之亦然。因此,您的循环依赖是“安全的”,因为没有任何东西会爆炸。
如果您追踪打印输出显示的路径,则不应阻止任何内容:
Go
(我怀疑)B
(static
构造函数),因为它未初始化。A.Initialize()
A
的{{1}}构造函数需要先执行static
B.Initialize()
不需要初始化,但它没有处于完成状态(幸运的是没有设置变量,因此没有任何中断)B
的{{1}}构造函数),然后返回A
,因为static
已初始化A.Initialize()
的{{1}}构造函数),然后返回A
你真正做的唯一事情就是存在一个不安全状态的可能性:访问一个构造函数没有完成执行的类。这是不安全的代码,虽然没有阻塞,但绝对代表一种破碎的状态。
答案 1 :(得分:1)
重要的是,只涉及一个线程。引自博文:
静态构造函数然后启动一个新线程。当该线程启动时,CLR会看到静态方法即将在其静态构造函数“在飞行中”另一个线程的类型上调用。它立即阻止新线程,以便在主线程完成运行类构造函数之前,Initialize方法不会启动。
在Erics示例中,有两个线程互相等待。你只有一个线程,所以没有等待的结果,因此:没有阻塞,没有死锁。
答案 2 :(得分:1)
为什么你认为应该存在僵局。静态构造函数只调用一次。无论执行语句B.Initialize();
多少次,它都会在第一次调用B时调用类B的静态构造函数。请参阅有关静态构造函数here的更多信息。
答案 3 :(得分:0)
静态方法只调用一次,一旦加载,则不再调用它们。
如果A.Initialize()
方法称为B.Initialize()
方法,那将是一个僵局,反之亦然。
所以任何类首次加载到内存中的静态块都会被执行,之后任何后续调用它 - 因为该类已经加载所以静态块不会被执行。
答案 4 :(得分:-3)
类实例实际上是在构造函数调用之前创建的,因此不再需要执行构造函数。