Что такое абстрактный класс в C#?

Ответ

Абстрактный класс в C# — это класс, который служит исключительно в качестве базового для других классов. Его нельзя инстанцировать напрямую с помощью оператора new. Основная цель — определить общий контракт (частичную реализацию) для группы родственных классов.

Ключевые характеристики:

  1. Нельзя создать экземпляр: var obj = new AbstractClass(); // Ошибка компиляции.
  2. Может содержать абстрактные члены: Методы, свойства, индексаторы или события без реализации. Они помечаются словом abstract и обязывают производные неабстрактные классы предоставить свою реализацию с помощью override.
  3. Может содержать обычные члены с реализацией: Поля, свойства, методы, конструкторы. Это позволяет вынести общую логику в базовый класс и избежать дублирования кода.
  4. Может иметь модификатор доступа: Например, protected abstract метод.

Пример, демонстрирующий общую логику и контракт:

using System;

abstract class Document
{
    // Обычное свойство с реализацией (общая логика)
    public string Title { get; set; }
    public string Author { get; set; }
    public DateTime Created { get; } = DateTime.Now;

    // Абстрактный метод (контракт, который нужно реализовать)
    public abstract void Print();

    // Обычный виртуальный метод (общая логика, которую можно переопределить)
    public virtual void Save()
    {
        Console.WriteLine($"Документ '{Title}' сохранён в базовом хранилище.");
    }

    // Конструктор в абстрактном классе допустим
    protected Document(string title, string author)
    {
        Title = title;
        Author = author;
    }
}

class PdfDocument : Document
{
    public int PageCount { get; set; }

    public PdfDocument(string title, string author) : base(title, author) { }

    // Реализация абстрактного метода ОБЯЗАТЕЛЬНА
    public override void Print()
    { 
        Console.WriteLine($"Печать PDF '{Title}' ({PageCount} стр.).");
    }

    // Переопределение виртуального метода (опционально)
    public override void Save()
    { 
        base.Save(); // Можно вызвать базовую реализацию
        Console.WriteLine("Дополнительно: зашифровано и добавлена цифровая подпись.");
    }
}

class WordDocument : Document
{
    public WordDocument(string title, string author) : base(title, author) { }
    public override void Print() 
    { 
        Console.WriteLine($"Печать Word-документа '{Title}'.");
    }
    // Метод Save не переопределён, будет использована реализация из базового класса.
}

// Использование:
Document doc1 = new PdfDocument("Отчёт", "Иван") { PageCount = 10 };
Document doc2 = new WordDocument("Письмо", "Анна");

doc1.Print(); // Печать PDF 'Отчёт' (10 стр.).
doc1.Save();  // Вызовет переопределённый Save из PdfDocument.
doc2.Print(); // Печать Word-документа 'Письмо'.
// doc2.Save(); // Вызовет базовый метод Save из Document.

Когда использовать абстрактный класс, а не интерфейс?

  • Абстрактный класс: Когда у производных классов есть чёткая иерархическая связь "is-a" (PdfDocument является Document) и нужно предоставить общую реализацию для части функциональности или состояние (поля).
  • Интерфейс: Когда нужно определить контракт, который могут реализовать несвязанные классы. C# 8.0+ позволяет добавлять реализацию по умолчанию и в интерфейсы, но они по-прежнему не могут содержать поля экземпляра.

Ответ 18+ 🔞

Да ты посмотри, что эти умники напридумывали! Абстрактный класс в C# — это как чертёж от инженера, который сам по себе хуй с горы, а не деталь. Взять его в руки и сказать «вот он, готовый агрегат» — нельзя, компилятор морду скривит. Но зато на его основе можно штамповать нормальные, рабочие классы. Ёпта, гениально же!

Что это за зверь такой, по пунктам:

  1. Создать экземпляр — нихуя. var obj = new AbstractClass(); — это прям билет в ошибку компиляции. Он как дух, материализоваться не может.
  2. Может орать «сделай это сам!». То есть содержать абстрактные члены (методы, свойства) с модным словечком abstract. И если твой класс от него наследуется и не хочет быть таким же абстрактным придурком, то он ОБЯЗАН эти методы переопределить через override. Иначе — пиздец, не скомпилируется.
  3. Но может и не только орать, а и делать. Может иметь обычные поля, свойства с реализацией, методы и даже конструкторы! Это чтобы общую для всех потомков логику не копировать десять раз, а запихнуть в одного предка. Удобно, чё.
  4. Может стесняться. Модификаторы доступа ему тоже доступны. protected abstract — это типа «сынок, этот метод только для семейства, но реализуй его сам».

Смотри, как это в жизни выглядит, на примере документов:

using System;

abstract class Document
{
    // Обычные свойства, есть у всех (общая логика)
    public string Title { get; set; }
    public string Author { get; set; }
    public DateTime Created { get; } = DateTime.Now;

    // Абстрактный метод (контракт, который нужно реализовать)
    public abstract void Print();

    // Обычный виртуальный метод (общая логика, которую МОЖНО, но не обязательно, переписать)
    public virtual void Save()
    {
        Console.WriteLine($"Документ '{Title}' сохранён в базовом хранилище.");
    }

    // Конструктор в абстрактном классе — да, бывает!
    protected Document(string title, string author)
    {
        Title = title;
        Author = author;
    }
}

class PdfDocument : Document
{
    public int PageCount { get; set; }

    public PdfDocument(string title, string author) : base(title, author) { }

    // Реализация абстрактного метода — ОБЯЗАТЕЛЬНА, иначе ты мудак
    public override void Print()
    { 
        Console.WriteLine($"Печать PDF '{Title}' ({PageCount} стр.).");
    }

    // Переопределение виртуального метода (захотел — сделал)
    public override void Save()
    { 
        base.Save(); // Можно и предка позвать, если не упёртый
        Console.WriteLine("Дополнительно: зашифровано и добавлена цифровая подпись.");
    }
}

class WordDocument : Document
{
    public WordDocument(string title, string author) : base(title, author) { }
    public override void Print() 
    { 
        Console.WriteLine($"Печать Word-документа '{Title}'.");
    }
    // Метод Save не переопределили — ну и хуй с ним, будет работать стандартный из Document.
}

// Использование:
Document doc1 = new PdfDocument("Отчёт", "Иван") { PageCount = 10 };
Document doc2 = new WordDocument("Письмо", "Анна");

doc1.Print(); // Печать PDF 'Отчёт' (10 стр.).
doc1.Save();  // Вызовет наш навороченный Save из PdfDocument.
doc2.Print(); // Печать Word-документа 'Письмо'.
// doc2.Save(); // А тут вызовется простенький Save из базового Document.

И главный вопрос: когда это использовать, а когда интерфейс?

  • Абстрактный класс: Когда у тебя есть чёткая родня по крови. «PDF-документ является документом». И когда этим детям нужно оставить в наследство не только долги (контракты), но и какое-то барахло (поля) и даже готовые рецепты (реализацию методов).
  • Интерфейс: Когда нужно определить контракт для кого угодно, даже для абсолютно левых, не родственных классов. Типа «и птица, и самолёт — летают». Да, в новых C# интерфейсы тоже научились иметь реализацию по умолчанию, но поля экземпляра у них всё равно нет, хитрожопые. Так что если нужно состояние — тебе сюда, в абстрактный класс.