Как именно работает Upcast в C#?

Из определения: Апкаст - приведение экземпляра производного класса к базовому

Фрагмент кода апкаста для примера. Класс Human производный от класса Mammal.

Human human = new Human();
Mammal mammal = (Mammal)human; //Upcast
  1. Если апкаст - это приведение экземпляра производного класса к базовому, то в данном случае экземпляр human класса Human должен был быть приведен к классу Mammal. Но после этого фрагмента кода у экземпляра human сохраняются методы и переменные производного класса. В чем тогда выражается приведение к базовому классу?
  2. Если апкаст - это создание нового экземпляра базового класса на основе экземпляра производного (в данном случае mammal из human), то в чем разница между указанным выше кодом с апкастом и данным кодом: Human human = new Human; Mammal mammal = new Mammal(); - ведь в итоге всё равно создается два экземпляра класса - производный и на основе его базовый?

P.S.: Я думаю, что мой вопрос не дублирует этот. Мой вопрос о технической стороне - почему апкаст был, а функционал остался как у производного класса.


Ответы (1 шт):

Автор решения: Igor Borzenkov
  1. Приведение типов никак не меняет содержимое объектов, т.е. при приведении не создаётся новых экземпляров классов.
  2. Вот здесь весьма подробно расписано в каких случаях необходимо использовать upcast. Но, пожалуй, в 95% случаев осуществлять явный upcast не имеет смысла. Компилятор C# весьма умный в этом отношении и приведение типов осуществляет неявным образом максимально комфортно для разработчиков.

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

Обратите внимание на строчку mammal.AnyHumanMethod(); Она вызывает ошибку компиляции. C# является строго типизированным языком. Компилятор следит, чтобы обращения ко всем членам класса производилось именно у того типа, к которому приведен объект.

class Program
{
    class Mammal
    {
        public virtual string Name => "It is a mammal";
    }

    class Human : Mammal
    {
        public override string Name => "It is a human";

        public void AnyHumanMethod()
        {
            Console.WriteLine("Method was called");
        }
    }
        
    static void Main()
    {
        var human = new Human();
        Console.WriteLine(human.Name); // Будет выведено "It is a human"
        human.AnyHumanMethod(); // Этот вызов корректен и допустим

        var mammal = (Mammal)human;
            
        // Сейчас всё равно будет вызван метод Human, т.к. несмотря на
        // приведение, в переменной mammal хранится экземпляр класса Human
        Console.WriteLine(mammal.Name); // Будет выведено "It is a human"
            
        // У класса Mammal нет метода AnyHumanMethod, он есть только в классе Human
        mammal.AnyHumanMethod(); // Ошибка компляции!

        // Никакого создания нового объекта не происходит при приведение типов.
        // Экземпляры классов human и mammal равны.
        Console.WriteLine($"Сейчас будет возвращено значение True: {human == mammal}");
    }
}
→ Ссылка