JavaRush /Курсы /C# SELF /Введение в наследование

Введение в наследование

C# SELF
12 уровень , 2 лекция
Открыта

1. Введение

Сегодняшняя тема может показаться немного абстрактной, но поверьте, без неё дальше никуда. Мы поговорим о наследовании в программировании. Сегодня мы просто его немного коснемся, а в будущем уже разберем детально.

Представьте, что вы пишете программу для учёта животных в зоопарке. У вас есть разные виды животных: львы, тигры, слоны, попугаи. Все они – животные. Но у них есть и свои особенности:

  • У льва есть грива.
  • Тигр полосатый.
  • Слон очень большой и у него хобот.
  • Попугай умеет говорить.

Если бы мы описывали каждое животное отдельно, код был бы очень похож:


// Лев
public class Lion
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Species { get; set; } = "Лев"; // Всегда "Лев"
    public void Eat() { Console.WriteLine("Лев ест мясо."); }
    public void Sleep() { Console.WriteLine("Лев спит."); }
    public void Roar() { Console.WriteLine("Лев рычит!"); }
    public string ManeColor { get; set; } // Особенность льва - грива
}

// Тигр
public class Tiger
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Species { get; set; } = "Тигр"; // Всегда "Тигр"
    public void Eat() { Console.WriteLine("Тигр ест мясо."); }
    public void Sleep() { Console.WriteLine("Тигр спит."); }
    public void Stride() { Console.WriteLine("Тигр крадется."); }
    public string StripePattern { get; set; } // Особенность тигра - полосатость
}

Видите, сколько дублирующегося кода? Name, Age, Eat(), Sleep() – это же общее для всех животных! А если у нас будет 100 видов животных? Кошмар!

Вот тут-то нам и поможет наследование.

2. Что такое наследование классов в C#?

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

Это как в реальной жизни: вы наследуете какие-то черты от своих родителей, но при этом у вас есть и свои уникальные особенности.

В C# наследование обозначается с помощью двоеточия : после имени дочернего класса:


            // Родительский (базовый) класс - Animal
public class Animal
{
    public string Name { get; set; } // Имя животного
    public int Age { get; set; }     // Возраст животного

    public void Eat()
    {
        Console.WriteLine($"{Name} ест.");
    }

    public void Sleep()
    {
        Console.WriteLine($"{Name} спит.");
    }
}

// Дочерний (производный) класс - Lion наследует от Animal
public class Lion : Animal // <-- Вот оно, наследование!
{
    public string ManeColor { get; set; } // Уникальное свойство льва

    public void Roar() // Уникальное поведение льва
    {
        Console.WriteLine($"{Name} рычит: РРРРРР!");
    }
}

// Еще один дочерний класс - Tiger наследует от Animal
public class Tiger : Animal
{
    public string StripePattern { get; set; } // Уникальное свойство тигра

    public void Stride() // Уникальное поведение тигра
    {
        Console.WriteLine($"{Name} крадется бесшумно.");
    }
}

Теперь, если мы создадим объект Lion или Tiger, у них автоматически будут свойства Name и Age, а также методы Eat() и Sleep(), потому что они их унаследовали от Animal.


class Program
{
    static void Main(string[] args)
    {
        Lion simba = new Lion();
        simba.Name = "Симба";
        simba.Age = 5;
        simba.ManeColor = "Золотой";

        simba.Eat();   // Метод унаследован от Animal
        simba.Sleep(); // Метод унаследован от Animal
        simba.Roar();  // Собственный метод Lion

        Console.WriteLine($"{simba.Name} - возраст: {simba.Age}, цвет гривы: {simba.ManeColor}");

        Tiger shereKhan = new Tiger();
        shereKhan.Name = "Шерхан";
        shereKhan.Age = 7;
        shereKhan.StripePattern = "Классический";

        shereKhan.Eat();   // Метод унаследован от Animal
        shereKhan.Sleep(); // Метод унаследован от Animal
        shereKhan.Stride(); // Собственный метод Tiger

        Console.WriteLine($"{shereKhan.Name} - возраст: {shereKhan.Age}, узор: {shereKhan.StripePattern}");
    }
}

Ключевые идеи наследования:

  1. Повторное использование кода (Code Reusability): Вы избегаете дублирования кода, помещая общую логику в базовый класс.
  2. Иерархия (Hierarchy): Создаётся логическая структура "общее-частное" (AnimalLion/Tiger).
  3. Полиморфизм (Polymorphism): (Пока просто упомянем, подробнее будет позже) Вы можете работать с объектами дочерних классов, используя ссылки на их базовый класс. Например, иметь список Animal и поместить туда и львов, и тигров, и потом вызывать у них метод Eat().

3. Вызов конструкторов базовых классов (base)

Когда вы создаёте объект дочернего класса (например, new Lion()), C# автоматически вызывает конструктор базового класса (в нашем случае Animal) перед вызовом конструктора дочернего класса. Это гарантирует, что базовая часть объекта будет правильно инициализирована.

Иногда вам нужно передать параметры в конструктор базового класса. Для этого используется ключевое слово base после сигнатуры конструктора дочернего класса.


public class Animal
{
    public string Name { get; set; }
    public int Age { get; set; }

    // Конструктор базового класса
    public Animal(string name, int age)
    {
        Name = name;
        Age = age;
        Console.WriteLine($"Конструктор Animal: Создано животное {Name}, возраст {Age}.");
    }

    public void Eat()
    {
        Console.WriteLine($"{Name} ест.");
    }

    public void Sleep()
    {
        Console.WriteLine($"{Name} спит.");
    }
}

public class Lion : Animal
{
    public string ManeColor { get; set; }

    // Конструктор Lion, который вызывает конструктор базового класса Animal
    public Lion(string name, int age, string maneColor) : base(name, age) // <-- Вот оно, 'base'!
    {
        ManeColor = maneColor;
        Console.WriteLine($"Конструктор Lion: Цвет гривы {ManeColor}.");
    }

    public void Roar()
    {
        Console.WriteLine($"{Name} рычит: РРРРРР!");
    }
}

            class Program
{
    static void Main(string[] args)
    {
        // При создании Lion, сначала вызывается конструктор Animal, потом Lion    
        Lion simba = new Lion("Симба", 5, "Золотой");
        simba.Roar();
    }
}

Что произойдет при выполнении new Lion("Симба", 5, "Золотой"):

  1. Будет вызван конструктор Lion(string name, int age, string maneColor).
  2. Благодаря : base(name, age), управление будет передано конструктору Animal(string name, int age).
  3. Выполнится код конструктора Animal, и вы увидите Конструктор Animal: Создано животное Симба, возраст 5.
  4. После этого управление вернётся в конструктор Lion.
  5. Выполнится код конструктора Lion, и вы увидите Конструктор Lion: Цвет гривы Золотой.

Это очень важно: базовый класс всегда инициализируется первым!

4. Операторы is и as

В мире программирования на C# вы будете часто сталкиваться с ситуациями, когда вам нужно проверить тип объекта или попытаться преобразовать его к другому типу. Здесь на помощь приходят операторы is и as. Они особенно полезны при работе с наследованием и полиморфизмом, о которых мы поговорим позже, но их основы можно понять уже сейчас.

Оператор is (проверка типа)

Оператор is позволяет проверить, является ли объект совместимым с определенным типом. Он возвращает true, если объект может быть преобразован к указанному типу, и false в противном случае.

Синтаксис: выражение is Тип


static void AnalyzeObject(object obj)
{
    if (obj is string)
    {
        Console.WriteLine("Это строка!");
    }
    else if (obj is int)
    {
        Console.WriteLine("Это целое число!");
    }
    else
    {
        Console.WriteLine("Это что-то другое.");
    }
}

static void Main()
{
    AnalyzeObject("Привет, мир!");      // Это строка!
    AnalyzeObject(123);                 // Это целое число!
    AnalyzeObject(3.14);                // Это что-то другое.
    AnalyzeObject(new int[] { 1, 2 });  // Это что-то другое.
}

is с сопоставлением с образцом:

Современный C# позволяет использовать is не только для проверки типа, но и для сразу же создания переменной этого типа, если тип совпадает. Это называется "сопоставлением с образцом" и делает код намного лаконичнее.


static void AnalyzeObjectWithPatternMatching(object obj)
{
    if (obj is string message) // Если obj - это строка, то присвоить её переменной message
    {
        Console.WriteLine($"Это строка, её длина: {message.Length}");
    }
    else if (obj is int number) // Если obj - это int, то присвоить его переменной number
    {
        Console.WriteLine($"Это число, умноженное на 2: {number * 2}");
    }
    else
    {
        Console.WriteLine("Тип не распознан.");
    }
}

static void Main()
{
    AnalyzeObjectWithPatternMatching("Пример"); // Это строка, её длина: 6
    AnalyzeObjectWithPatternMatching(50);       // Это число, умноженное на 2: 100
    AnalyzeObjectWithPatternMatching(new object()); // Тип не распознан.
}

Этот подход с is и сопоставлением с образцом гораздо предпочтительнее, чем традиционное приведение типов с последующей проверкой на null, так как он более безопасен и читаем.

Оператор as (безопасное приведение типа)

Оператор as пытается преобразовать объект к указанному типу. Если преобразование возможно, он возвращает объект, приведенный к этому типу. Если преобразование невозможно (то есть объект несовместим с целевым типом), as возвращает null вместо того, чтобы выбрасывать исключение. Это делает as "безопасным" оператором приведения.

Синтаксис: выражение as Тип


static void ProcessData(object data)
{
    string text = data as string; // Пытаемся привести data к string
    
    if (text != null) // Проверяем, удалось ли приведение
    {
        Console.WriteLine($"Обработка строки: {text.ToUpper()}");
    }
    else
    {
        Console.WriteLine("Данные не являются строкой.");
    }
}

static void Main()
{
    ProcessData("hello");      // Обработка строки: HELLO
    ProcessData(123);          // Данные не являются строкой.
    ProcessData(null);         // Данные не являются строкой.
}

Когда использовать as вместо прямого приведения (Тип)выражение:

  • Безопасность: Если вы не уверены, что объект действительно является нужного типа, as позволит избежать InvalidCastException и вместо этого получить null, который можно обработать.
  • С ссылочными типами: as работает только с ссылочными типами (классы, интерфейсы, делегаты, массивы). Он не может быть использован для преобразования значимых типов (например, int к double).

Важно: Если вы уверены, что объект всегда будет нужного типа, или если вам нужно преобразовать значимый тип, используйте прямое приведение (Тип)выражение. Если приведение невозможно, прямое приведение сгенерирует ошибку (исключение InvalidCastException), что может быть желаемым поведением, если неверный тип является ошибкой в логике программы.

2
Задача
C# SELF, 12 уровень, 2 лекция
Недоступна
Создание базового класса и его наследников
Создание базового класса и его наследников
2
Задача
C# SELF, 12 уровень, 2 лекция
Недоступна
Конструкторы в наследуемых классах
Конструкторы в наследуемых классах
Комментарии (2)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Slevin Уровень 56
27 января 2026
Логическая структура лекции плоха. Base упомянут мимолетом, зато прыжок в стороны IS / AS. Имхо, разбить на две лекции. Дополнить про Base.
Ra Уровень 35 Student
1 октября 2025
Почему-то явно не написали, что для обращения к суперклассу используется слово base (аналог super в Java)