Моя C# программа не инициализирует Объект так, как мне кажется, в соответствии с порядком инициализации Объекта. Почему? - PullRequest
0 голосов
/ 11 января 2020

Я считаю, что порядок инициализации объекта C# выглядит следующим образом:

  • Производные данные c поля
  • Производные данные c Конструктор
  • Производные поля экземпляра
  • Базовые данные c поля
  • Базовые данные c конструктор
  • Базовые поля экземпляра
  • Базовый конструктор экземпляра
  • Производный конструктор экземпляра

Ниже вы видите простую тестовую программу и вывод, который она выдает при запуске.

    public class UiBase
    {
        protected static string Something = "Hello";

        public UiBase()
        {
            Console.WriteLine(this.ToString());
        }
    }

    public class Point : UiBase
    {
        private int X = -1;
        private int Y = -1;

        static Point()
        {
            Something = "Bar";
        }

        public Point(int x, int y)
        {
            X = x;
            Y = y;
        }

        public override string ToString()
        {
            return $"Point:{X}/{Y}/{Something}";
        }
    }

    public static class Program{
    public static void Main(){
        var x = new Point(2,1);
        Console.WriteLine(x);
    }
on Console:
Point:-1/-1/Bar
Point:2/1/Bar

Когда я думаю о том, как это должно происходить в соответствии с список выше, я считаю, что это должно быть так:

  1. Point stati c поле (нет в моем случае?)
  2. Point stati c конструктор -> устанавливает Something на "Штрих"
  3. Поля экземпляра точки
  4. Базовое состояние c поля -> Возвращает что-то в "Hello"? ...

Однако, это НЕ устанавливает что-то обратно в Hello, что действительно смущает меня. Так как я могу это объяснить? или Инициализация объекта отличается от того, что я сказал?

1 Ответ

6 голосов
/ 11 января 2020

Вы вызываете виртуального члена 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 инициализации.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...