在将只包含静态方法的类重构为声明为 static
类后,我遇到了一些奇怪的问题,这导致应用程序启动时出现了问题。
虽然我没有进行任何彻底的调查,但似乎静态构造函数中发生的某些调用由于某种原因未能完成。
因此,我想知道在使用 C# 中的静态构造函数时是否存在任何陷阱?更具体地说,是否有任何必须绝对避免并且不得从静态构造函数中使用的东西?
静态构造函数存在几个陷阱。例如,如果一个静态构造函数抛出异常,每当您访问其任何成员时,都会继续收到TypeInitializationException
。
如果一个静态构造函数抛出异常,运行时将不会再次调用它,并且该类型将在应用程序域的整个生命周期中保持未初始化状态。
通常情况下,静态类只应在无状态场景中使用,其中您不需要任何初始化。如果您的类需要初始化,您可能最好使用单例模式,可以在第一次访问时延迟初始化:
public class MyClass
{
private static readonly Lazy<MyClass> current =
new Lazy<MyClass>(() => new MyClass());
public static MyClass Current
{
get { return current.Value; }
}
private MyClass()
{
// Initialization goes here.
}
public void Foo()
{
// ...
}
public void Bar()
{
// ...
}
}
static void Main(string[] args)
{
MyClass.Current.Foo(); // Initialization only performed here.
MyClass.Current.Bar();
MyClass.Current.Foo();
}
编辑:我进一步阅读了相关内容,发现如果在静态构造函数中执行阻塞操作(例如异步回调或线程同步),它们确实会导致死锁。
CLR在内部使用锁定来防止同时执行类型初始值设定项(静态构造函数)。因此,如果您的静态构造函数尝试从另一个线程访问其声明类型的另一个成员,则不可避免地会发生死锁。由于“另一个成员”可能是作为PLINQ或TPL操作的一部分声明的匿名函数,因此这些错误可能会很微妙且难以识别。
Igor Ostrovsky (MSFT)在他的Static constructor deadlocks文章中解释了这一点,并提供了以下死锁示例:
using System.Threading;
class MyClass
{
static void Main() { /* Won’t run... the static constructor deadlocks */ }
static MyClass()
{
Thread thread = new Thread(arg => { });
thread.Start();
thread.Join();
}
}
{ }
,该函数被定义为其回调函数。然而,由于匿名函数在幕后被编译为另一个私有方法MyClass
,所以新线程在MyClass
类型初始化之前无法访问它。并且,由于MyClass
静态构造函数需要等待新线程先完成(因为thread.Join()
),导致死锁发生。是的,有一些陷阱,大多与类何时初始化有关。基本上,具有静态构造函数的类将不被标记为 beforefieldinit
标志,这使得运行时可以在稍后的时间初始化它。
更多细节请参见此文章。
这不是对问题的回答,但它太长了不能作为评论,所以我在这里提供它...
由于我不知道static class
构造,我使用以下方案(简化)来提供单例:
public class SomeSingleton {
static _instance;
static public SomeSingleton Instance {
get {
if (_instance==null) {
_instance=new SomeSingleton();
}
return _instance;
}
}
}
之后,您可以使用
SomeSingleton.Instance.MyProp = 3;
第一次使用 Instance
成员将构造您的单例模式。
我猜这是可以的,因为如果有许多这样的类的单例模式实例化是按适当的顺序完成的。
Instance
属性被多个线程同时访问,它们可能会得到“单例”的不同实例。在特定情况下,这可能或者不是一个问题,但它总体上破坏了单例模式。如果你使用的是.NET 4(或者更高版本),应该切换到Lazy<T>
;如果不是,则应该考虑使用lock
来同步初始化。 - Douglas
private static readonly Lazy<MyClass> current; static MyClass { current = new Lazy<MyClass>(() => new MyClass()); }
- MarkLazy
分配给静态字段(而不是初始化它),因此它没有失败的机会(即使MyClass()
构造函数抛出异常)。在这两种情况下,单例只有在第一次调用MyClass.Current
时才会被初始化(懒加载)。 - Douglas