Подтвердить что ты не робот

Потенциальные ловушки со статическими конструкторами в С#

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

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

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

4b9b3361

Ответ 1

Есть несколько подводных камней для статических конструкторов. Например, если статический конструктор генерирует исключение, вы будете продолжать получать 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, эти ошибки могут быть тонкими и трудно идентифицируемыми.

Игорь Островский (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()), произойдет затвор.

Ответ 2

Да, есть некоторые подводные камни, в основном связанные с тем, когда класс инициализирован. В принципе класс со статическим конструктором не будет отмечен флагом beforefieldinit, который позволяет среде выполнения инициализировать его позднее.

Посмотрите эту статью для более подробной информации.

Ответ 3

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

Поскольку я не знал для конструкции 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 построит ваш синглтон.

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