C#中静态构造函数的潜在缺陷

lys*_*cid 18 .net c# clr static

我的问题是在重构一个只包含要声明为static类的静态方法的类之后,并在启动应用程序时遇到奇怪的问题.

我没有进行任何彻底的调查,但似乎从静态构造函数中进行的某些调用由于某种原因没有完成.

那么,我想知道在C#中使用静态构造函数时有哪些陷阱?更具体地说,是否有任何事情应该不惜一切代价避免,而不是在静态构造函数中使用?

Dou*_*las 25

静态构造函数有几个缺陷.例如,如果静态构造函数抛出异常,则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();
}
Run Code Online (Sandbox Code Playgroud)

编辑:我做了一些关于此事的进一步阅读,如果你在其中执行阻塞操作(例如异步回调或线程同步),静态构造函数似乎确实会导致死锁.

CLR内部使用锁定来防止类型初始化器(静态构造函数)同时执行多次.因此,如果您的静态构造函数尝试从另一个线程访问其声明类型的另一个成员,则它将不可避免地死锁.由于"另一个成员"可能是一个声明为PLINQ或TPL操作一部分的匿名函数,因此这些错误可能很微妙且很难识别.

Igor Ostrovsky(MSFT)在他的Static构造函数死锁文章中解释了这一点,提供了以下死锁示例:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

在上面的示例中,新线程需要访问空的匿名函数{ },定义为其回调函数.但是,由于匿名函数被编译为MyClass幕后的另一个私有方法,因此新的线程在MyClass类型初始化之前无法访问它.并且,由于MyClass静态构造函数需要等待新线程首先完成(因为thread.Join()),因此会出现死锁.