JavaRush /Курсы /C# SELF /Абстрактные методы и свойства

Абстрактные методы и свойства

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

1. Абстрактные методы

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

Теперь давайте углубимся в детали и рассмотрим более сложные и практичные сценарии. В этой лекции мы сосредоточимся на нюансах синтаксиса, на том, как абстракция работает в многоуровневых иерархиях, и четко разграничим, когда использовать abstract, а когда — virtual.

Абстрактный метод похож на загадочный рецепт из старой кулинарной книги: "добавьте секретный ингредиент" — какой, не сказано, но все последующие повара обязаны что-то придумать!

Абстрактный метод всегда находится в абстрактном классе

Попытка объявить абстрактный метод в обычном (не-абстрактном) классе приведёт к ошибке компилятора. Почему? Потому что обычный класс можно создавать напрямую, а что мы получим при попытке вызвать неописанный метод? Только загадочный взгляд IDE.


// Это вызовет ошибку компиляции:
public class WrongClass
{
    public abstract void Oops(); // Нельзя так делать!
}

Если класс содержит хотя бы один абстрактный метод, он обязан быть помечен как abstract!

Реализация абстрактных методов в производных классах

Реализация производится через ключевое слово override. Класс, который наследует абстрактный класс и не реализует все абстрактные методы, тоже обязан быть абстрактным (иначе компилятор возмутится).

Продолжим идею нашего приложения:

В предыдущих лекциях мы создали класс Shape (фигура), в котором объявляли абстрактный метод для вычисления площади. Теперь создадим конкретные фигуры:


public class Rectangle : Shape
{
    public double Width { get; }
    public double Height { get; }

    public Rectangle(double width, double height)
    {
        Width = width;
        Height = height;
    }

    public override double CalculateArea()
    {
        return Width * Height;
    }
}

public class Circle : Shape
{
    public double Radius { get; }

    public Circle(double radius)
    {
        Radius = radius;
    }

    public override double CalculateArea()
    {
        return Math.PI * Radius * Radius;
    }
}

Почему абстрактные методы так важны?

Абстрактные методы — это ваш способ сказать: "Ребята, если вы наследуете этот класс, определитесь, как это должно работать!" Это делает архитектуру программы более чёткой, защищает от случайных пропусков, и, главное, позволяет использовать полиморфизм на полную катушку.

2. Абстрактные свойства (properties)

Что такое абстрактное свойство

Абстрактное свойство — это такой же контракт, как и абстрактный метод, только для свойства (property). Для C# это очень естественно, потому что свойства — один из главных способов инкапсулировать данные. Абстрактное свойство говорит: "Дайте потомкам самим решать, где брать (и, возможно, как задавать) значение этого свойства".

Пример:
Добавим свойство Name для фигуры — оно должно быть у каждого потомка, но пусть каждый решает, что там вернуть.


public abstract class Shape
{
    public abstract string Name { get; }

    public abstract double CalculateArea();
}

Теперь каждый потомок обязан реализовать это свойство:


public class Rectangle : Shape
{
    public double Width { get; }
    public double Height { get; }
    public override string Name => "Прямоугольник";

    public Rectangle(double width, double height)
    {
        Width = width;
        Height = height;
    }

    public override double CalculateArea()
    {
        return Width * Height;
    }
}

public class Circle : Shape
{
    public double Radius { get; }
    public override string Name => "Круг";

    public Circle(double radius)
    {
        Radius = radius;
    }

    public override double CalculateArea()
    {
        return Math.PI * Radius * Radius;
    }
}

Особенности синтаксиса

Абстрактное свойство похоже на интерфейсное: оно объявляется без тела-реализации, но с указанием, будет ли у него только getter, или ещё и setter.


public abstract class Creature
{
    // read-only свойство
    public abstract string Species { get; }

    // read-write свойство
    public abstract int Age { get; set; }
}

В реализующем классе можно определить логику получения (и при необходимости — изменения) значения.

Зачем нужны абстрактные свойства

Абстрактное свойство — отличный инструмент, когда вы хотите, чтобы каждый потомок сам решал, как получать или хранить значение. Это часто нужно в моделировании, при работе с бизнес-моделями, вью-моделями, и вообще там, где логика работы со свойством сложнее, чем просто возврат поля.

Например, если у одной фигуры имя вычисляется по формуле, а у другой — возвращается константой, всё это можно красиво "спрятать" за абстрактным свойством.

3. Схема: как связаны абстрактные класс, методы и свойства

Давайте посмотрим на следующую схему, чтобы увидеть, как это работает:


┌─────────────┐
│ abstract    │
│   Shape     │
│-------------│
│ +Name: str  │  <-- abstract property
│ +Area(): dbl│  <-- abstract method
└─────┬───────┘
      │
 ┌────▼────┐      ┌───────┐
 │Rectangle│      │ Circle│
 │........ │ .... │ ......│
 │+Name    │      │+Name  │
 │+Area()  │      │+Area()│
 └─────────┘      └───────┘

Так мы обеспечиваем ОДИН интерфейс работы с любым потомком класса Shape, но каждый потомок волен делать "под капотом" что угодно.

UML-диаграмма для абстрактного метода и свойства:


┌────────────────────────────┐
│        abstract class      │
│           Animal           │
│────────────────────────────│
│+ Name: string {abstract}   │
│+ MakeSound(): void {abstract}│
└─────────────┬──────────────┘
              │
     ┌────────┴──────────┐
     │                   │
┌────────────┐     ┌─────────────┐
│    Cat     │     │    Dog      │
│────────────│     │─────────────│
│+ Name      │     │+ Name       │
│+ MakeSound()│    │+ MakeSound()│
└────────────┘     └─────────────┘

4. Практические сценарии и польза для реальных проектов

Абстрактные методы и свойства — ваш инструмент, если вы проектируете иерархии классов, которые должны развиваться. Это фундамент для крупных бизнес-приложений, многоуровневых моделей предметных областей, плагин-систем, UI-фреймворков, где для оснований системы задаётся "закон", обязательный для всех потомков.

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

Вопрос "зачем" и возвращение к полиморфизму

Полиморфизм кажется магией, но на самом деле это просто контракт: "ты можешь вызывать некий метод у любых объектов семейства, и всегда получишь подходящий результат". Абстрактные методы и свойства — способ заставить всю иерархию поддерживать общий язык, но без жёсткой реализации "по умолчанию".

Такой подход активно используется в системах плагинов (IDE, графические редакторы, CRM-системы), когда сторонние разработчики создают свои расширения, но обязаны реализовать минимальный набор функций, требуемых платформой. Например, если вы пишете модуль для обработки файлов, базовый класс может содержать абстрактное свойство FileExtension и абстрактный метод Open(), чтобы любой плагин мог корректно обрабатывать файлы своего типа.

Различия между абстрактными, виртуальными и обычными членами класса

Характеристика Обычный метод/свойство virtual abstract
Наличие реализации Есть Есть Нет
Обязательность override Нет Нет (переопределять можно) Да (обязательно в потомке)
Можно ли вызвать напрямую Да Да Нет
Может ли быть в обычном классе Да Да Нет
Может быть помечен sealed Нет Да Нет

5. Применение в нашем учебном приложении

Теперь, вооружившись новыми знаниями, давайте вернемся к нашему приложению с фигурами и посмотрим, как абстрактные свойства и методы работают вместе, чтобы создать гибкую и понятную систему.


// Например, в основном методе программы
List<Shape> shapes = new List<Shape>
{
    new Rectangle(4, 5),
    new Circle(2.5)
};

foreach (Shape shape in shapes)
{
    // Благодаря абстрактному свойству Name и методу CalculateArea,
    // мы не заботимся о конкретном типе фигуры
    Console.WriteLine($"{shape.Name}, Площадь: {shape.CalculateArea():F2}");
}

Результат:

Прямоугольник, Площадь: 20.00
Круг, Площадь: 19.63

Красота: код не знает и не заботится, кто именно фигура — он просто вызывает нужные свойства и методы, а .NET CLR позаботится о правильной реализации!

6. Частые ошибки и особенности реализации

Ошибка №1: не реализован абстрактный метод родителя.
Если в производном классе хотя бы один абстрактный метод или свойство остались без реализации, и сам класс при этом не помечен как abstract, компилятор не даст собрать проект. Это полезная защита: вы не сможете создать объект с "дырявой" логикой — например, фигурой без метода CalculateArea().

Ошибка №2: метод объявлен как abstract, а класс — нет.
Такой код тоже не компилируется. Если вы добавили в класс abstract-метод, значит, сам класс должен быть абстрактным:


public abstract class Polygon : Shape
{
    // Не реализуем CalculateArea(), класс остается abstract
}

Ошибка №3: попытка реализовать абстрактный метод через virtual.
Абстрактный метод должен быть реализован с ключевым словом override, а не virtual. Однако если вы хотите, чтобы реализацию могли переопределять дальше по иерархии, вы можете сначала переопределить метод, а затем уже объявить его как virtual в дочернем классе:


public override double CalculateArea()
{
    // Реализация по умолчанию...
}

В более глубоком потомке такой метод уже можно переопределить заново. Но сделать его снова abstract — нельзя, так как реализация уже есть.

2
Задача
C# SELF, 22 уровень, 2 лекция
Недоступна
Объявление абстрактного класса и метода
Объявление абстрактного класса и метода
2
Задача
C# SELF, 22 уровень, 2 лекция
Недоступна
Абстрактные методы и свойства
Абстрактные методы и свойства
Комментарии (3)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Aleksei Perchukov Уровень 66
19 августа 2025
virtual sealed это как? Сама концепция virtual же противоречит sealed
Ильнур Уровень 62
27 августа 2025
Тут речь об использовании в потомках. Можно запретить дальнейшее переопределение. Но хотя да, не совсем понятно написано.
Mark Marchenko Уровень 27
10 января 2026
Да, бессмысленно... В обоих случаях метод базового класса нельзя пометить как sealed, и в обоих случаях override можно пометить как sealed.

virtual sealed void Foo() // Так нельзя
abstract sealed void Bar() // Так нельзя

sealed override void Foo() // Так можно
sealed override void Bar() // Так можно