Виртуализация в конструкторе суперклассов

Я придерживался мнения, что виртуализация не работает в конструкторе суперкласса в соответствии с дизайном ООП. Например, рассмотрим следующий код C#.

using System;
namespace Problem
{
    public class BaseClass 
    {
        public BaseClass() 
        {
            Console.WriteLine("Hello, World!");
            this.PrintRandom();
        }
        public virtual void PrintRandom() 
        {
            Console.WriteLine("0");
        }
    }

    public class Descendent : BaseClass 
    {
        private Random randomValue;
        public Descendent() 
        {
            Console.WriteLine("Bonjour, Monde!");
            randomValue = new Random();
        }
        public override void PrintRandom() 
        {
            Console.WriteLine(randomValue.NextDouble().ToString());
        }

        public static void Main() 
        {
            Descendent obj = new Descendent();
            obj.PrintRandom();
            Console.ReadLine();
        }
    }
}

Этот код ломается, потому что, когда создается объект Descendent, он вызывает конструктор базового класса, и у нас есть вызов виртуального метода в конструкторе базового класса, который, в свою очередь, вызывает метод производного класса и, следовательно, он дает сбой, поскольку randomValue не инициализирован к тому времени .

Аналогичный код работает в C++, потому что вызов PrintRandom не направляется в производный класс, поскольку IMO, порядок в C++ выглядит примерно так:


1. вызов конструктора базового класса
2. Обновление V - таблица для этого класса
3. вызвать код конструктора

Мой вопрос заключается в том, что, во-первых, прав ли я в том, что согласно принципам ООП виртуализация не должна / не работать в конструкторе суперкласса, а во-вторых, если я прав, то почему поведение отличается на всех языках .NET (у меня есть протестировал его с C#, VB.NET и MC++)

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
0
1 973
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

В собственном C++ программа работает так, как ожидалось: вы получаете вызов версии виртуальной функции базового класса в конструкторе базового класса. Во время вызова конструктора существует только базовый класс и его виртуальные функции, поэтому вы получаете версию самой нижней виртуальной функции, определенной в то время. Это не означает, что виртуализацию нельзя использовать, вы просто не получите подклассовые версии виртуальных методов в конструкторах базовых классов (вот почему это не рекомендуется).

Очевидно, как вы можете видеть, управляемый код работает по-другому, потому что (iirc) весь объект создается до вызова конструкторов, и, таким образом, вы получаете виртуальную функцию подкласса перед конструктором подкласса. Это задокументированное различие между поведением языков, но оно должно быть согласованным для всех языков .NET (поскольку все они компилируются в один и тот же IL).

На мой взгляд, это не вопрос объектно-ориентированных принципов - вопрос о том, как она справляется с этой конкретной загадкой, зависит от конкретной платформы. Вызов виртуального метода из конструктора не рекомендуется именно по этой причине, однако - если вы собираетесь это сделать, вам нужно явно задокументировать очень, что вы собираетесь его вызывать, чтобы любой класс, переопределяющий его, знал, чего ожидать. .

Java использует тот же подход, что и .NET Кроме, в котором в C# любые инициализаторы переменных экземпляра выполняются до вызова базового конструктора. Это означает, что в вашем конкретном примере вы можете исправить код, инициализировав random в точке объявления. В Java это не поможет.

Что касается того, почему MC++ не работает таким образом, я не знаю - я предлагаю вам сравнить сгенерированный IL. Я предполагаю, что он явно вызывает не виртуальный метод.

Обновлено: Я подозреваю, что неправильно прочитал вопрос - как работает MC++? Если он работает так, как работает C#, это хорошо, IMO, обеспечивая единообразное представление о платформе .NET.

Я бы предложил использовать FxCop в вашем коде. Я работал со многими людьми, которые отклоняли пункты, поднятые этим инструментом, как несущественные, но, если ваш код содержит множество незначительных проблем (например, ваш), то шансы быть укушенным одним или несколькими намного выше.

Анализ кода ReSharper также поднимет эту конкретную проблему.

Другие вопросы по теме