Всем привет! Многие сталкиваются с трудностями на собеседовании на вопросе по типу "Расскажите о порядке иницализации в C#". Либо банально когда видят квиз, стараются вспомнить, а что там должно инициализироваться? Сегодня многие вспомнят, кто-то узнает о порядке инициализации. Затронем не только классы, а также структуры, а точнее - ключевое слово default для них.
Сделаем следующие классы и посмотрим, что будет при создании объекта B:
class A
{
public int a = Foo("pole a");
public static int a1 = Foo($"вывод константы {con}\npole static a1");
public const int con = 2;
public A()
{
Console.WriteLine("const A");
}
static A()
{
Console.WriteLine("static const A");
}
public static int Foo(string c)
{
Console.WriteLine(c);
return 1;
}
}
class B:A
{
public int b = Foo("pole b");
public static int b1 = Foo("pole const b1");
public B()
{
Console.WriteLine("const B");
}
static B()
{
Console.WriteLine("static const B");
}
}
Единственное что нужно запомнить - каждый шаг работает с фукнцией Foo(она служит оповещением о пройденном этапе). У нас есть константа (c неё и начнём). Она определяется на этапе КОМПИЛЯЦИИ. То есть её значение мы узнаем первыми.
Но что дальше? А дальше - нужно запомнить пару вещей (которые все давно помнят, но как это работает под капотом - не многие задумывались).
Сначала в бой идут статические конструкторы. Знает уже каждый. Но что же с полями? А всё просто - запоминаем простыми словами: "В констуркторе сначала идет инициализация полей пользователем, а дальше сама работа констурктора". То есть в Low-level коде всё выглядит попроще. Берем класс A:
internal class A
{
public int a;
public static int a1;
public const int con = 2;
public A()
{
this.a = A.Foo("pole a");
base..ctor();
Console.WriteLine("const A");
}
static A()
{
DefaultInterpolatedStringHandler interpolatedStringHandler = new DefaultInterpolatedStringHandler(31, 1);
interpolatedStringHandler.AppendLiteral("вывод константы ");
interpolatedStringHandler.AppendFormatted<int>(2);
interpolatedStringHandler.AppendLiteral("\npole static a1");
A.a1 = A.Foo(interpolatedStringHandler.ToStringAndClear());
Console.WriteLine("static const A");
}
[NullableContext(1)]
public static int Foo(string c)
{
Console.WriteLine(c);
return 1;
}
}
Наши поля, которым мы задали значения - пусты (кроме константы)! Они инициализированы по умолчанию. А присвоение им значений происходит в соответствующих конструкторах (обычное поле - к обычному конструктору, статик - к статику). Теперь выставляем по полочкам:
Идёт сначала константа, далее статик конструктор (в нём сначала инициализация поля, далее работа конструктора), а после - обычный конструктор (логика та же, что и в статике).
Но у нас 2 класса, создаём мы дочерний класс, что нам выведется в итоге?
Давайте посмотрим на класс B в Low-level code:
internal class B : A
{
public int b;
public static int b1;
public B()
{
this.b = A.Foo("pole b");
base..ctor();
Console.WriteLine("const B");
}
static B()
{
B.b1 = A.Foo("pole const b1");
Console.WriteLine("static const B");
}
}
Всё знакомо, видим (и помним!), что статик конструкторы не наследуются. А теперь смотрим на обычный конструктор - видим, что также сначала идет инициализация обычного поля b, а потом - вызывается конструктор родителя (если много наследований, всё будет также, получается "конструктор в конструкторе" для каждого класса). В конструкторе родителя (мы его смотрели выше) будет:
public A()
{
this.a = A.Foo("pole a");
base..ctor();
Console.WriteLine("const A");
}
То есть Foo для обычного поля, потом работа конструктора. Значит, после поля b мы приступим не к конструктору B, а к полю a.
Итоговый вывод:
вывод константы 2
pole static a1
static const A
pole const b1
static const B
pole b
pole a
const A
const B
Получается: идет работа статик конструкторов классов по очереди от родительского класса (тк мы в классе B можем брать стат поля из класса A, всё логично), далее идёт инициализация поля b, потом работа конструктора A (поле a + сам конструктор) и только потом работа конструктора B.
Выглядит громоздко, но запоминаем 2 вещи - сначала идут все статик конструкторы, потом обычные констуркторы + что такое конструктор в Low-level C# (работа с полем + наш конструктор).
А теперь к структуре - всё то же самое. Единственное слово default многих вводит в ступор. Вспоминаем, оно присваивает переменной дефолт значение. Для int - 0, для типов допускащих налл - null. Но что же со структурами?
Проверяем:
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
A a = default;
Console.WriteLine(A.a1);
Console.WriteLine(a.a);
}
private A a1;
}
}
struct A
{
public int a = Foo("pole a");
public static int a1 = Foo($"вывод константы {con}\npole static a1");
public const int con = 2;
public A()
{
Console.WriteLine("const A");
}
static A()
{
Console.WriteLine("static const A");
}
public static int Foo(string c)
{
Console.WriteLine(c);
return 1;
}
}
Создаём по той же идее СТРУКТУРУ A и тестим, заметьте, поле без вызова конструктора и присвоение default - одно и то же по записи, это всё дефолт.
Запускаем
вывод константы 2
pole static a1
static const A
1
0
Обычный конструктор - не запускается, как и статик (его работа начинается, только если будем взаимодействовать с статик полем, поэтому вывод у статик поля - 1). Конструктор не изменил значение НЕстатик поля (вывод 0 в конце) и сам не сработал. Дефолт ставит значения всех полей структуры по умолчанию, что может быть неожиданно (особенно если поле класса - структра, и мы забудем у него явно активировать конструктор). На этом и закончим, запомните, что такое конструктор и будет вам счастье!