Как стать автором
Обновить

Жизненный цикл статического класса в C#

Статический класс мало чем отличается от обычного класса. Разве что:

  • От него нельзя наследоваться и он сам не может наследоваться от любого класса, кроме Object

  • Нельзя создавать экземпляр класса

  • Может содержать только статические члены

Программа не может точно сказать когда будет осуществляться вызов статического класса. Однако гарантируется загрузка этого класса, инициализация его полей и вызов статического конструктора перед первым обращением к классу в программе. Когда статический класс загружен, его метаданные могут быть выгружены и кешированы. За кэшированным он остается в памяти на время существования домена приложения, в котором находится программа. Место хранение кэша зависит от среды выполнения.

О статических полях

Каждое статическое поле хранится в куче, независимо от того, объявлены ли она в ссылочном типе или в типе значения. Для неё будет создан только один участок памяти, независимо от того, сколько экземпляров будет создано. Обратите внимание, что эта куча отделена от обычной кучи со сбором мусора — она известна как «High Frequency Heap» и существует по одному для каждого домена приложения. Чиститься только после завершение программы. Если мы хотим очистить раньше, то требуется пометить статическое поле как null и тогда сборщик мусора очистит это поле.

Статический конструктор

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

static internal class Number
{
  static private readonly int[] _numbers = new int[] { 1, 2, 3 };	

  static Number()
	{
		Console.WriteLine($"Всего есть {_numbers.Length} цифры");
	}

	static public void First()
	{
		Console.WriteLine(_numbers[0]);
	}

	static public void Second()
	{
		Console.WriteLine(_numbers[1]);
	}
}

В классе Main пропишем:

Number.First();
Number.Second();

И получим:

Вызов статических методов

(из книги Джеффри Рихтера “CLR via C#. 4-е издание”)

Вызов статического метода генерирует инструкцию вызова в промежуточном языке Microsoft (MSIL).

Для выполнения какого-либо метода, его IL-код (или MSIL) должен быть преобразован в машинные команды. Этим занимается JIT-компилятор.

Непосредственно перед исполнением метода Main среда CLR находит все типы данных, на которые ссылается программный код метода Main. При этом CLR выделяет внутренние структуры данных, используемые для управления доступом к типам, на которые есть ссылки. На рисунке ниже метод Main ссылается на единственный тип — Console, и среда CLR выделяет единственную внутреннюю структуру. Эта внутренняя структура данных содержит по одной записи для каждого метода, определенного в типе Console. Каждая запись содержит адрес, по которому можно найти реализацию метода. При инициализации этой структуры CLR заносит в каждую запись адрес внутренней недокументированной функции, содержащейся в самой среде CLR. Я обозначаю эту функцию JITCompiler.

Когда метод Main первый раз обращается к методу WriteLine, вызывается функция JITCompiler. Она отвечает за компиляцию IL-кода вызываемого метода в собственные команды процессора. Поскольку IL-код компилируется непосредственно перед выполнением («just in time»), этот компонент CLR часто называют JIT-компилятором. Функции JITCompiler известен вызываемый метод и тип, в котором он определен. JITCompiler ищет в метаданных соответствующей сборки IL-код вызываемого метода. Затем JITCompiler проверяет, компилирует IL-код в машинные команды, которые сохраняются в динамически выделенном блоке памяти. После этого JITCompiler возвращается к структуре внутренних данных типа, созданной средой CLR, и заменяет адрес вызываемого метода адресом блока памяти, содержащего готовые машинные команды. В завершение JITCompiler передает управление коду в этом блоке памяти. Этот программный код является реализацией метода WriteLine (вариант этого метода с параметром String). Из этого метода управление возвращается в метод Main, который продолжает выполнение в обычном порядке. Рассмотрим повторное обращение метода Main к методу WriteLine. К этому моменту код метода WriteLine уже проверен и скомпилирован, так что обращение к блоку памяти производится напрямую, без вызова JITCompiler. Отработав, метод WriteLine возвращает управление методу Main. На рис. 1.5 показано, как выглядит ситуация при повторном обращении к методу WriteLine.

JIT-компилятор хранит машинные команды в динамической памяти. Это значит, что скомпилированный код уничтожается по завершении работы приложения. Для повторного вызова приложения или для параллельного запуска его второго экземпляра (в другом процессе операционной системы) JIT-компилятору придется заново скомпилировать IL-код в машинные команды.

Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.