Потенциальные проблемы с использованием статических конструкторов в C#


Мой вопрос возникает после рефакторинга класса, который содержал только статические методы, которые должны быть объявлены как класс static, и испытывает странные проблемы при запуске приложения.

Я не проводил тщательного исследования, но похоже, что некоторые вызовы, выполняемые из статического конструктора, по какой-то причине не завершаются.

Итак, я хотел бы знать, где есть какие-либо подводные камни при использовании статических конструкторов в C#? Более конкретно, есть ли какие-либо вещи, которые следует избегать любой ценой и не использовать внутри статического конструктора?

3   18  

3 ответа:

У статических конструкторов есть несколько подводных камней. Например, если статический конструктор создает исключение, вы будете продолжать получать 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();
}

Edit: я немного углубился в этот вопрос, и оказалось, что статические конструкторы действительно вызывают взаимоблокировки, если вы выполняете в них блокирующие операции (например, асинхронные обратные вызовы или синхронизацию потоков).

Среда CLR внутренне использует блокировку для предотвращения одновременного многократного выполнения инициализаторов типов (статических конструкторов). Таким образом, если ваш статический конструктор попытается получить доступ к другому члену своего объявляющего типа из другого потока, это неизбежно приведет к взаимоблокировке. Поскольку "другой член" может быть анонимной функцией, объявленной как часть операции PLINQ или TPL, эти ошибки могут быть тонкими и трудными для идентификации.

Игорь Островский (MSFT) объясняет это в своей книге: статический конструктор 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 построит ваш синглтон.

Я думаю, что это нормально, так как создание экземпляров синглетов, если таких классов много, выполняется в правильном порядке.