вторник, 25 марта 2014 г.

Статические конструкторы в .Net

Многие знают, что в .Net есть такая вещь, как статические конструкторы. И то, что для статических конструкторов CLR гарантирует две вещи:

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

В общем-то из-за второго свойства статические конструкторы обычно используют для создания объектов по шаблону Singleton. Примерно вот так:
    
    public class SampleSingleton
    {
        private static SampleSingleton instance;

        public static SampleSingleton Instance
        {
            get
            {
                return instance;
            }
        }

        private SampleSingleton() { }

        static SampleSingleton()
        {
            instance = new SampleSingleton();
        }

        public void SomeMethod()
        {
            Console.WriteLine("блаблабла");
        }
    }

А дальше начинается то, о чем не многие задумываются. Как вы думаете реализуется эта "одноразовость" вызова статического конструктора? Реализуется она банально захватом внутренней блокировки из чего следует потенциальная возможность устроить себе дедлок, который будет не очень просто отлаживать.  Вот, посмотрите на этот код:


    class SomeClass
    {
        public static int val = GetValue();

        public static int GetSomeOtherValue() 
        {
            return 123;
        }

        public static int GetValue()
        {
            return Task.Factory.StartNew(() => GetSomeOtherValue()).Result;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(SomeClass.val.ToString());
            Console.WriteLine("Main");
        }
    }

Слово "Main" вы не увидите никогда. Потому, что здесь происходит классический дедлок. Статический конструктор для инициализации типа захватывает блокировку, и при инициализации вызывает создает новый поток, ожидая его завершения. А в новом потоке, как как тип SomeClass еще не проинициализирован начинается повторная его инициализация с попыткой захвата блокировки.

И, кстати, если в процессе выполнения статического конструктора выбрасывается исключение, то тип остается не проинициализированным и, соответственно, не работоспособным до конца выполнения программы.

Ну и самая интересная и неоднозначная особенность статических конструкторов касается одной особенности внутренней работы компилятора и CLR. Особенность в том, то если класс не содержит явного объявления статического конструктора, то класс при компиляции помечается атрибутом BeforeFieldInit, наличие которого говорит о том, что инициализация статических полей может происходить когда угодно до момента первого их использования. Ну, или вообще может не происходить. Например, попробуйте запустить вот этот код без отладчика (Ctrl+F5):
class StaticTest
    {
        public static int value = GetValue();

        public static int GetValue()
        {
            Console.WriteLine("Инициализация");
            return 1;
        }

        public static void StaticMethod()
        {
            Console.WriteLine("Статический метод");

        }

        //static StaticTest()
        //{
        //    Console.WriteLine("Статический конструктор");
        //}

        public StaticTest()
        {
            Console.WriteLine("Конструктор");
        }

        public void InstanceMethod()
        {
            Console.WriteLine("Метод экземпляра");
        }

    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Main");
            StaticTest t = new StaticTest();
            t.InstanceMethod();
            Console.ReadLine();
        }
    }

Как видите, статические поля так и не были проинициализированы до конца выполнения программы. Если же разкомментировать статический конструктор, то результат будет ровно тем, который и ожидается:
Main
Инициализация
Статический конструктор
Конструктор
Метод экземпляра

Так что, помимо всего прочего наличие у класса статического конструктора гарантирует строго определенную последовательность инициализации типов. В, в общем-то, предсказуемое время.

Комментариев нет:

Отправить комментарий