Вы вызываете виртуального члена ToString()
в базовом UiBase
конструкторе класса
Console.WriteLine(this.ToString());
Он вызывается раньше Point
constructor
public Point(int x, int y)
{
X = x;
Y = y;
}
this
не полностью инициализирован, вы получаете -1
на выходе. Поскольку ToString()
является виртуальным методом, вызывается Point.ToString()
, согласно specs
Вызывается переопределяющий член в наиболее производном классе, который может быть исходным членом, если ни один производный класс не переопределил член.
Конструктор Stati c вызывается автоматически перед созданием экземпляра Point
или ссылками на все члены stati c (посмотрите на stati c конструкторы для деталей)
static Point()
{
Something = "Bar";
}
Он перезапишет Something
из базового класса, и вы получите Bar
в выходных данных в обоих случаях. Something
никогда не устанавливается обратно на Hello
, оно перезаписывается только один раз.
Something
поле полностью указано c для UiBase
, в классе Point
нет копии, оно значение будет изменено везде. Согласно stati c членов
Существует только одна копия члена stati c, независимо от того, сколько экземпляров класса создано.
Если вы напечатаете UiBase.Something
после Console.WriteLine(x);
, вы получите Bar
, а не Hello
. Существует только одно исключение для обобщенных c классов, но оно выходит за рамки вашего вопроса.
С точки зрения порядка выполнения все инициализаторы полей выполняются в порядке от производного класса к базе, а затем запускаются все конструкторы в порядке от базы к производной (это верно для членов экземпляра). Я добавил шаги для всех ваших операций, чтобы увидеть фактический порядок.
public class UiBase
{
private static int temp = Step("uibase static field init");
public static string Something = "Hello";
private int _temp = Step("uibase instance field init");
public static int Step(string message)
{
Console.WriteLine(message);
return 0;
}
public UiBase()
{
Step("uibase instance ctor");
Console.WriteLine(this.ToString());
}
}
public class Point : UiBase
{
private int _temp = Step("point instance field init");
private int X = -1;
private int Y = -1;
static Point()
{
Step("point static ctor before");
Something = "Bar";
Step("point static ctor after");
}
public Point(int x, int y)
{
Step("point instance ctor");
X = x;
Y = y;
}
public override string ToString()
{
return $"Point:{X}/{Y}/{Something}";
}
}
вывод будет следующим
point static ctor before
uibase static field init
point static ctor after
point instance field init
uibase instance field init
uibase instance ctor
Point:-1/-1/Bar
point instance ctor
Point:2/1/Bar
Конструктор Point
stati c сначала вызывается (нет полей stati c в классе Point
), затем он «запрашивает» UiBase
inita поля stati c, потому что обращается к его значению Something
(установлено Hello
) ), после этого Something
устанавливается на Bar
и выполнение продолжается до инициализации экземпляра (опять же, Something
больше не меняется) - поля производного класса, поля базового класса, конструктор базового класса и конструктор производного класса.
Я думаю, только первые 3 строки могут немного сбить с толку, но инициализация stati c происходит только один раз и перед любой инициализацией экземпляра. Порядок инициализации stati c определяется компилятором на основе вашего фактического кода.
Добавление конструктора UiBase
stati c может фактически сделать картинку более четкой, в данном случае UiBase
stati c участников будут инициализированы до Point
stati c инициализации.