Страницы

Поиск по вопросам

среда, 27 ноября 2019 г.

Можно ли обойтись без статического конструктора?

#c_sharp #.net


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

Но зачем он нужен, если я могу присвоить эти значения при объявлении переменных?
    


Ответы

Ответ 1



В языке C# существует несколько способов инициализации полей: В месте объявления В конструкторе Вот наивный пример: class Foo { private string s1 = "s1"; private string s2; private static string s3 = "s3"; private static string s4; public Foo() { s2 = "s2"; } static Foo() { s4 = "s4"; } } Если смотреть с высоты птичьего полета, то инициализация по месту реализуется довольно просто: выражение, использованое для инициализации поля переносится в соответствующий конструктор - в экземплярный конструктор для экземплярны полей и в статический конструктор для статических полей. Тут нужно сказать, почему у нас два конструктора. Конструктор экземпляра - это некоторая вспомогательная функция, призванная инициализировать экземпляр создаваемого объекта. Например, мы можем сказать, что любой валидный объект должен обладать некоторым поведением (инвариантом) и конструктор - это специальная функция, обязанная его обеспечить. Инвариантом может быть что угодно: начиная от того, что некоторое поле не нулевое, заканчивая более сложными правилами, например, что сумма полей дебет и кредит равна 0. Помимо инвариантов объектов существуют еще и инварианты типа: т.е. некоторые условия, которые должны быть верными не для конкретных объектов, но для типа целиком. Поведение типа выражается с помощью статических членов, а значит "инвариант" типа - это валидное состояние статичесих переменных, ответственность за валидность которых обеспечивает статический конструктор. В отличие от конструктора объекта конструктор типа не вызывается пользователем. Вместо этого, он вызывается CLR в момент первого обращения к типу (опустим точные правила). Существует тонкие различия в поведении во время исполнения, которое отличает использование инициализатора полей в месте объявления от инициализации этих же полей в конструкторе. Инициализаторы статических и экземплярных полей - это синтаксический сахар, но очень важно знать, какой именно! Разница в случае экземплярных полей и конструкторов Все инициализаторы экземплярных полей перемещаются компилятором C# в экземплярных конструктор. Но главный вопрос здесь: куда именно. В общих чертах конструктор экземпляра выглядит следующим образом: // ctor // Код инициализаторов полей // Вызов конструктора базового класса // Код текущего конструктора У данного алгоритма есть два важных следствия. Во-первых, код инициализаторов экземплярных полей не просто помещается в конструктор, он помещается в самое его начало, еще до вызова конструктора базового класса, и во-вторых, он будет скопирован во все экезмплярные конструкторы. Первое замечание очень важно (да, об этом могут спросить на собеседовании и это может пригодиться в реальных приложениях). Например, если кто-то вздумает вызвать виртуальный метод в конструкторе базового класса, то часть полей будет проинициализированы, а часть нет. Несложно догадаться, что поля, проинициализированные в месте объявления уже будут валидными, а другие поля будут содержать дефолтные значения. Да, да, да, вызывать виртуальные методы в конструкторах базового класса - это плохо, но в реальность такое бывает и нужно понимать, что в этом случае будет в рантайме. Разница в случае статических полей и конструкторов Со статическими конструкторами дела обстоят несколько сложнее и проще одновременно. С точки зрения наследования, способ инициализации статических полей никак не пересекается с вызовом статического конструктора базового класса. Никак. Там вообще процесс вызова статических конструкторов отличается от экземплярных. Например, при создании экземпляра наследника вначале вызывается статический конструктор наследника, а потом статический конструктор базового класса. А если дергается статический метод наследника, то статический конструктор базового класса вообще не вызовется автоматом (вызовется только если статический метод наследника как-то дернет базовый тип). Но сложность возникает с тем, когда именно будет вызван статический конструктор. Как уже написал @Qwertiy наличие или отсутствие статического конструктора в классе влияет на то, когда будет вызван этот самый конструктор. Наличие статического конструктора приводит к генерации странного специального флага, который затем скажет CLR, что можно более вольно относиться к времени вызова статического конструктора, и сделать это теперь можно будет не прямо перед первым обращением, а, например, перед вызовом метода, в котором это обращение происходит. Потенциально, это может повлиять на эффективность приложения, поскольку теперь проверка будет делаться один раз, а не тысячи раз, при условии, что первое обращение находится в цикле от 0 до 1000. Заключение Инциализатор полей (статических и нет) - это сахар, но он может быть с горчинкой, если не понимать, к чему приводит его чрезмерное использование. Обычно я пользуюсь следующим эмпирическим правилом. Для экземплярных полей: нужно инициализировать поле аргументом конструктора - (без вариантов) использую конструктор; в противном случае - инициазатор полей. В случае статических полей: в подавляющем числе случаев использую инициализатор. Если кода много, то выделяю метод. Если мне нужно использовать статический конструктор, чтобы задать порядок инициализации или изменить семантику инициализации типа, то я добавляю огромный комментарий, который говорит, почему к этому фрагменту кода нужно относитсья очень внимательно. Если мне нужно использовать инициализатор для экземплярных полей, чтобы инициализация прошла до вызова конструктора базового класса, то я рефакторю код, чтобы этого было не нужно. Например, выделяю фабриный метод. Если же такоей поведение действительно нужно, то тут нужен двухстраничный комментарий, который объясняет почему это нужно и почему другие варианты не подходят.

Ответ 2



Существует две гарантии вызова статических конструкторов: Базовая гарантия инициализации типа: конструктор типа будет вызван перед созданием экземпляра или перед первым обращением к статическому члену. "Расслабленная" (relaxed) гарантия инициализация типа (BeforeFieldInit): конструктор типа будет вызван при первом обращении к статическому полю (справедливо для .NET 4.0+) и может быть вызван когда угодно до первого обращения к типу. Для того, чтобы CLR использовала «расслабленную» модель, тип должен быть помечен флагом BeforeFieldInit. Для языка C# выбор определяется наличием или отсутствием явного статического конструктора: при наличии явного статического конструктора используется базовая модель инициализации типа, а при отсутствии статического конструктора – расслабленная модель. public sealed class Singleton { private static readonly Singleton instance = new Singleton(); // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit static Singleton() { } private Singleton() { } public static Singleton Instance { get { return instance; } } } Практика показывает, что в большинстве случаев при отсутствии явного конструктора, JIT-компилятор вызывает инициализатор статических переменных непосредственно перед вызовом метода, в котором используется эта переменная. Если же использовать статический конструктор класса Singleton, то поведение будет именно таким, которое ожидает большинство разработчиков – инициализатор поля вызван не будет до первого обращения. Источники: http://sergeyteplyakov.blogspot.ru/2013/07/blog-post.html http://sergeyteplyakov.blogspot.ru/2011/08/blog-post.html http://sergeyteplyakov.blogspot.ru/2013/06/blog-post_28.html

Ответ 3



Не каждый объект можно инициализировать в одну строку. К примеру, часто используемый мною класс XmlSerializerNamespaces требует после вызова конструктора несколько раз вызвать метод Add. Или, если нужно построить делегат через Linq Expressions - приходится отдельно создавать параметры, хотя все остальное выражение обычно можно записать в одну строку. Иногда требуется и более сложная логика инициализации. Кроме того, надо понимать, что это в C# инициализаторы статических полей отличаются от статического конструктора. В процессе же компиляции они все попросту дописываются в его начало.

Ответ 4



Суть статического конструктора в том, что он вызывается один единственный раз и до создания первого элемента указанного типа. Вы конечно можете делать инициализацию каждый раз при создании экземпляра с дополнительной проверкой, но зачем? Но зачем он нужен, если я могу присвоить эти значения при объявлении переменных? Он может быть полезен, если важен порядок инициализации статических полей или для выполнения каких-то дополнительных действий, которые не происходят при простой инициализации типа для которого используется статический член. Например: using System; class A { public A() { Console.WriteLine("A"); } } class B { public B() { Console.WriteLine("B"); } } public class Test { static readonly A a; static readonly B b; public static void Main() { } static Test() { b = new B(); Console.WriteLine("-"); a = new A(); } } Т.о. мы изменили порядок создания статических полей и добавили выполнение дополнительного действия, чего нельзя было бы добиться при простой инициализации по месту.

Ответ 5



Если рассматривать статический класс как шаблон - то это "халявный" синглтон, и все статические члены класса - члены данного синглтона. При этом у данного статического класса при передаче ему экземпляра не статического класса будет полный доступ к полям этого не статического класса. Статический класс также называют классом ТИПА (а статический конструктор конструктором типа соответственно), это значит что даже если класс объявлен не статическим, для него по факту будет создана статическая составляющая. Ну а дальше надо исходить из удобства и необходимости: "ЭКЗЕМПЛЯР" СТАТИЧЕСКОГО КЛАССА: 1. Нет наследования 2. Доступен везде без передачи какой либо ссылки в код функции (согласно области видимости) ЭКЗЕМПЛЯР НЕ СТАТИЧЕСКОГО КЛАССА: 1. Есть наследование 2. Можно иметь несколько экземпляров 3. Доступен только по ссылке переданной в функцию

Ответ 6



К примеру у вас есть класс internal class SomeType { private static Int32 s_x = 5; } этот код будет равносильным коду internal class SomeType { private static Int32 s_x; static SomeType() { s_x = 5; } } Если вы используете 1й вариант, то компилятор автоматически сгенерирует конструктор типа

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

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