Статические конструкторы (класса)

Тема статических конструкторов уже затрагивалась в разделе “Поля”, но давайте рассмотрим эти конструкторы еще раз внимательнее. Класс может иметь максимум один статический конструктор, и этот статический конструктор не может принимать параметров. Статические конструкторы никогда не вызываются напрямую.

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

Давайте модифицируем предыдущий пример, добавив статический конструктор, и посмотрим, как изменится вывод:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using System;
public class A {
static A ()
Console.WriteLine ( "static A::A()" );
private static int InitX ()
Console.WriteLine ( "A.InitX()" );
return 1;
private static int InitY0
Console. WriteLine ( "A.InitY0" );
return 2;
private static int InitA0
Console. WriteLine ( "A.InitA0" );
return 3;
private static int InitB () {
Console. WriteLine ( "A.InitB0" );
return 4;
}
private int у = InitY() ;
private int x = InitX () ;
private static int a = InitA();
private static int b = InitB0 ;
}
public class EntryPoint {
static void Main 0 {
A a = new A0;
}
}

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

Вот что получилось в результате:

А.InitA0
A.InitB0
static А::А()
A.InitY0
A.InitX0

Разумеется, статические конструкторы были вызваны перед созданием экземпляра класса. Однако обратите внимание на последовательность вызовов. Инициализаторы статических полей выполняются перед выполнением тела статического конструктора. Это гарантирует правильную инициализацию полей перед тем. как произойдет обращение к ним из тела статического конструктора.

Вызов инициализатора типа (реализованного с использованием синтаксиса статического конструктора) перед обращением к любому из членов этого типа является стандартным поведением CLR. Здесь имеется в виду, что инициализаторы типа будут выполняться перед тем, как код обратится к полю или методу класса или перед созданием объекта этого класса. Чтобы несколько ослабить строгость этих правил, можно применить определенный в CLR атрибут метаданных beforefieldinit.

В отсутствие атрибута beforefieldinit среда CLR вызывает инициализатор типа перед тем, как будет затронут любой член класса. С помощью атрибута beforefieldinit среде CLR разрешается отложить инициализацию типа до момента, непосредственно предшествующего первому обращению к статическому полю, но не раньше.

Это значит, что если для класса установлен атрибут beforefieldinit, то можно вызывать конструкторы экземпляра и методы, не требуя предварительного выполнения инициализатора. Но как только кто-нибудь попытается обратиться к статическому полю класса, CLR перед этим вызовет инициализатор типа.

Имейте в виду, что атрибут beforefieldinit разрешает CLR отложить инициализацию типа на более позднее время, но CLR все равно может инициализировать тип задолго до первого обращения к статическому полю.

Компилятор С# устанавливает атрибут beforefieldinit для всех классов, в которых специально не определены статические конструкторы. Чтобы убедиться в этом, просмотрите в ILDASM сгенерированный код IL для предыдущих двух примеров. В примере из предыдущего раздела, где не был определен статический конструктор, метаданные класса А выглядят следующим образом:

1
2
3
4
.class public auto ansi beforefieldinit A
extends [mscorlib]System.Object
{
} // end of class A

Метаданные класса А из примера в настоящем разделе выглядят иначе:

1
2
3
4
.class public auto ansi A
extends [mscorlib]System.Object
{
} // end of class A

Такое поведение компилятора C# имеет большой смысл. При явном определении инициализатора типа обычно необходимо гарантировать, что он будет выполнен перед использованием чего-либо из класса или перед созданием любого экземпляра этого класса. Однако если явный инициализатор типа не предусмотрен, но есть инициализаторы статических полей, то компилятор С# создаст такой инициализатор типа, который просто инициализирует все статические поля.

Поскольку пользовательский код для инициализатора типа не предоставлен, компилятор С# может позволить классу отложить инициализацию статического поля до момента первого обращения к этому полю.

После обсуждения атрибута beforefieldinit вы должны отметить одну важную вещь. Предположим, что есть класс, подобный рассмотренным в примерах, где статическое поле инициализируется результатом вызова метода. Если этот класс не имеет явного инициализатора типа, будет ошибкой предположить, что код, вызванный во время инициализации статического поля, будет вызван перед созданием объекта этого класса. Например, рассмотрим следующий код:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System;
public class A {
public A() {
Console. WriteLine ( "A.AO" );
}
static int InitX() {
Console.WriteLine ( "A.InitX0" );
return 1;
}
public int x = InitX ();
}
public class EntryPoint {
static void Main 0 {
// Нет гарантий вызова A.InitX () перед этим! A a = new AO;
}
}

Если реализация InitX содержит некоторые сторонние эффекты, которые должны быть выполнены перед созданием экземпляра объекта данного класса, то такой код лучше поместить в статический конструктор, чтобы компилятор не применял к классу атрибут метаданных beforefieldinit. В противном случае нет никаких гарантий, что код со сторонним эффектом будет запущен до создания экземпляра класса.

Вы можете следить за любыми ответами на эту запись через RSS 2.0 ленту. Вы можете оставить ответ, или trackback с вашего собственного сайта.

Оставьте отзыв

XHTML: Вы можете использовать следующие теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

 
Rambler's Top100