В этой статье мы поговорим о статических конструкторах в C#. Сначала немного теории. Что же такое статический конструктор и для чего он используется?
Статический конструктор нельзя вызывать напрямую. Если обычный конструктор вызывается в момент создания экземпляра класса, то статический – вызывается автоматически перед созданием первого экземпляра класса или перед использованием ссылки на какие-либо статические члены. Инициализация статических переменных в коде C# компилируется CLR в статический конструктор, различие его от конструктора, заданного в явном виде лишь в одном атрибуте MS IL, как раз и определяющего логику вызова (отложено/при обращении).
Такой конструктор бывает полезен для выполнения каких-либо подготовительных действий, которые должны быть выполнены только один раз. В статическом конструкторе можно задать начальные значения для статических переменных класса, проинициализировать компоненты, применяемых ко всему классу, а не к отдельному экземпляру объекта этого класса. Также типичным использованием статических конструкторов является случай, когда класс использует файл журнала и конструктор применяется для добавления записей в этот файл. Статические конструкторы бывают полезны при создании классов-оболочек для неуправляемого кода, когда конструктор может вызвать метод LoadLibrary.
Примечание: как и в отношении многих вещей в .NET Runtime, пользователь не может знать заранее, в какой момент будет вызван статический конструктор. .NET Runtime гарантирует только то, что он будет вызван где-то в промежутке между моментом запуска программы и созданием первого объекта данного класса. Это, в частности, означает, что в статическом конструкторе нельзя предполагать, что объект будет создан в самое ближайшее время.
Если статический конструктор инициирует исключение, среда выполнения не вызывает его во второй раз, и тип остается неинициализированным на время существования домена приложения, в котором выполняется программа. Ещё хотелось отметить, что статический конструктор не принимает модификаторы доступа и не имеет параметров.
Чтобы объявить статический конструктор, достаточно просто добавить модификатор static перед объявлением конструктора. Статический конструктор не может иметь параметров.
class MyClass {
static MyClass() {
//...
}
}
|
Для деструкторов никаких статических аналогов не существует.
Теперь, когда нам известна теория, попробуем сделать задание. Рассмотрим следующий код и попробуем ответить на следующие вопросы: Что произойдет при запуске программы?
1. Ошибка компиляции. В каком месте?
2. Ошибка выполнения. В каком месте? Что успеет вывестись на консоль?
3. Ошибок не будет. Что будет выведено на консоль?
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Threading;
using System.Runtime.InteropServices;
namespace Tests
{
class Program
{
#region StaticConstructor&Inherit
public interface IWritable
{
string Write();
}
public class A : IWritable
{
public static int intVarA;
static A()
{
intVarA = C.intVarC + B.intVarB + 5;
}
public virtual string Write()
{
return "A.Write()";
}
}
public class B : A, IWritable
{
public static int intVarB;
static B()
{
intVarB = C.intVarC + A.intVarA + 5;
}
public override string Write()
{
return "B.Write()";
}
}
public class C : B, IWritable
{
public static int intVarC;
static C()
{
intVarC = B.intVarB + A.intVarA + 5;
}
public new string Write()
{
return "C.Write()";
}
}
#endregion
static void Main(string[] args)
{
#region StaticConstructor&Inherit
Console.WriteLine(A.intVarA);
Console.WriteLine(B.intVarB);
Console.WriteLine(C.intVarC);
Console.WriteLine();
#endregion
Console.ReadLine();
}
}
}
|
Ответ: На консоли будет выведено три числа: 20, 5, 10.
Почему вывод в консоли будет именно такой, давайте разберёмся. Сначала когда вызывается строчка Console.WriteLine(A.intVarA); в консоль должно вывестись значение статической переменной A.intVarA, по умолчанию переменные типа int инициализируются нулём но в классе A присутствует статический конструктор, который как мы уже знаем будет запущен до обращения к любому члену класса A.
static A()
{
intVarA = C.intVarC + B.intVarB + 5;
}
|
В этом конструкторе используются статические переменные C.intVarC и B.intVarB. Классы B и С также имеют статические конструкторы и мы заходим в них до инициализации статических переменных. Сначала в конструктор класса С.
static C()
{
intVarC = B.intVarB + A.intVarA + 5;
}
|
Тут, чтобы посчитать переменную intVarC, компилятору необходимо выполнить статический конструктор класса В.
static B()
{
intVarB = C.intVarC + A.intVarA + 5;
}
|
Здесь мы опять видим переменные C.intVarC и A.intVarA, похоже на зацикливание. J НО как мы помним, статический конструктор вызывается только один раз! А у нас в программе все статические конструкторы уже были вызваны, поэтому больше вызываться статические конструкторы не будут. Вспомним чему у нас равна переменная C.intVarC= B.intVarB + A.intVarA + 5 = 0 + 0 = 5. Т.к. по умолчанию int инициализируются нулём. Аналогично можно вычислить intVarC = B.intVarB + A.intVarA + 5 = 5 + 0 + 5 = 10. Осталось вычислить intVarA = C.intVarC + B.intVarB + 5 = 10 + 5 + 5 = 20. Теперь, я надеюсь, всё понятно, почему программа вывела такие числа на консоль. Если объяснение не очень понятно, то попробуете подебажить программу по шагам и всё поймёте.
Надеюсь, статья была познавательной и интересной для вас. Следующая статья будет про наследование в C#.
Сначала же заходим в конструктор класса B, получается intVarB = C.intVarC + A.intVarA + 5 = 0 + 0 + 5 = 5, далее идем в конструктор класса С, intVarC = B.intVarB + A.intVarA + 5 = 0 + 5 + 5 = 10. Ну и в последний заходим в А, intVarA = C.intVarC + B.intVarB + 5 = 10 + 5 + 5 = 20, и получаем логичный ответ 20, 5, 10.
ОтветитьУдалитьСуть примера в том, чтобы понять что статический конструктор вызывается только один раз до обращения к любому члену класса. Поэтому не будет ошибки или зацикливания.
УдалитьЭтот комментарий был удален автором.
ОтветитьУдалитьВ теории суть ясна, но осознание пришло на идеальном примере.
ОтветитьУдалить