Отслеживание версий констант и статических полей только для чтения
Константы и поля только для чтения имеют разную бинарную семантику отслеживания версий. Если выражение ссылается на константу, значение этой константы получается во время компиляции, но если выражение ссылается на поле только для чтения, значение этого поля известно только во время выполнения. Рассмотрим приложение, состоящее из двух отдельных программ.
using System;
namespace Program1
{
public class Utils
{
public static readonly int X = 1;
}
}
namespace Program2
{
class Test
{
static void Main() {
Console.WriteLine(Program1.Utils.X);
}
}
}
Пространства имен Program1 и Program2 обозначают две программы, компилируемые отдельно. Поскольку Program1.Utils.X объявлено как статическое поле только для чтения, выходное значение оператора Console.WriteLine не известно во время компиляции, но получается во время выполнения. Таким образом, если значение X изменяется и Program1 компилируется повторно, оператор Console.WriteLine выведет новое значение, даже если Program2 не компилируется повторно. Но если бы X был константой, значение X было бы получено при компиляции Program2 и осталось бы независимым от изменений в Program1 до перекомпиляции Program2.
Поля с модификатором volatile
Если объявление поля включает модификатор volatile, поля, введенные этим объявлением, являются полями с модификатором volatile.
Способы оптимизации, переупорядочивающие инструкции, для полей не-volatile могут привести к непредвиденным и непредсказуемым результатам в многопоточных программах, которые обращаются к полям без синхронизации, такой как предоставляемая оператором блокировки (§8.12). Эти оптимизации могут выполняться компилятором, системой поддержки выполнения или оборудованием. Для полей volatile такие переупорядочивающие оптимизации ограничены.
· чтение поля volatile называется чтением volatile. У чтения volatile имеется «семантика захвата», то есть, оно гарантированно выполняется прежде любых обращений к памяти, расположенных после него в последовательности инструкций;
· запись поля volatile называется записью volatile. У записи volatile имеется «семантика освобождения», то есть, оно гарантированно выполняется после всех обращений к памяти, расположенных до инструкции записи в последовательности инструкций.
Эти ограничения приводят к тому, что все потоки будут видеть записи volatile, выполняемые любым другим потоком, в том порядке, в каком они выполнялись. Не требуется соответствующая реализация для обеспечения единого общего упорядочения записей volatile, представляемого для всех потоков выполнения. Поле volatile должно имеет один из следующих типов:
· ссылочный тип;
· тип byte, sbyte, short, ushort, int, uint, char, float, bool, System.IntPtr или System.UIntPtr;
· перечисляемый тип, имеющий базовый перечисляемый тип byte, sbyte, short, ushort, int или uint.
Пример:
using System;
using System.Threading;
class Test
{
public static int result;
public static volatile bool finished;
static void Thread2() {
result = 143;
finished = true;
}
static void Main() {
finished = false;
// Run Thread2() in a new thread
new Thread(new ThreadStart(Thread2)).Start();
// Wait for Thread2 to signal that it has a result by setting
// finished to true.
for (;;) {
if (finished) {
Console.WriteLine("result = {0}", result);
return;
}
}
}
}
дает на выходе:
result = 143
В этом примере метод Main запускает новый поток, выполняющий метод Thread2. Этот метод сохраняет значение в поле не-volatile с именем result, затем сохраняет true в поле volatile с именем finished. Главный поток ожидает, пока в поле finished не будет установлено значение true, затем считывает поле result. Так как поле finished объявлено как volatile, главный поток должен прочитать значение 143 из поля result. Если бы поле finished не было объявлено как volatile, то сохранение в result могло быть видимым в главном потоке после сохранения в finished, и главный поток мог прочитать значение 0 из поля result. Объявление поля finished как volatile устраняет такую несогласованность.
Инициализация поля
Начальным значением поля, как статического, так и поля экземпляра, является значение по умолчанию (§5.2) типа поля. Невозможно видеть значение поля до выполнения этой инициализации по умолчанию, и поэтому поле никогда не бывает «неинициализированным». Пример:
using System;
class Test
{
static bool b;
int i;
static void Main() {
Test t = new Test();
Console.WriteLine("b = {0}, i = {1}", b, t.i);
}
}
производятся следующие выходные данные
b = False, i = 0
так как и b, и i автоматически инициализированы значениями по умолчанию.
Инициализаторы переменных
Объявления полей могут включать инициализаторы переменных. Для статических полей инициализаторы переменных соответствуют операторам присвоения, выполняемым во время инициализации класса. Для полей экземпляров инициализаторы переменных соответствуют операторам присваивания, выполняемым при создании экземпляра класса.
Пример:
using System;
class Test
{
static double x = Math.Sqrt(2.0);
int i = 100;
string s = "Hello";
static void Main() {
Test a = new Test();
Console.WriteLine("x = {0}, i = {1}, s = {2}", x, a.i, a.s);
}
}
производятся следующие выходные данные
x = 1.4142135623731, i = 100, s = Hello
так как присваивание для x происходит при выполнении инициализаторов статического поля, а присваивания для i и s происходят при выполнении инициализаторов поля экземпляра.
Инициализация значения по умолчанию, описанная в §10.5.4, происходит для всех полей, включая поля, имеющие инициализаторы переменных. Таким образом, когда класс инициализируется, все статические поля в этом классе сначала инициализируются своими значениями по умолчанию, а затем выполняются инициализаторы статических полей в текстовом порядке. Аналогично, когда создается экземпляр класса, все поля в этом экземпляре сначала инициализируются своими значениями по умолчанию, а затем выполняются инициализаторы полей экземпляров в текстовом порядке.
Статические поля с инициализаторами переменных можно наблюдать в их состоянии со значениями по умолчанию. Однако такой стиль настоятельно не рекомендуется. Пример:
using System;
class Test
{
static int a = b + 1;
static int b = a + 1;
static void Main() {
Console.WriteLine("a = {0}, b = {1}", a, b);
}
}
В этом примере показывается такое поведение. Несмотря на циклические определения a и b, программа допустима. Она дает на выходе
a = 1, b = 2
так как статические поля a и b инициализированы значением 0 (значение по умолчанию для int) до выполнения их инициализаторов. Когда выполняется инициализатор для a, значение b равно нулю, так что a инициализируется значением 1. Когда выполняется инициализатор для b, значение a уже равно 1, так что b инициализируется значением 2.