Рассказывает Akhil Mittal
В прошлых статьях серии «Введение в ООП» мы рассматривали полиморфизм (а также его нюансы на практике) и наследование. В этой мы поговорим о самой захватывающей части ООП-парадигмы — об абстрактных классах. В целом концепция абстрактных классов в C# ничем не отличается от таковой в других языках, но в C# работать с ней приходится несколько иначе.
Что такое абстрактные классы
В плане терминологии давайте доверимся MSDN:
Модификатор abstract указывает, что реализация сущности с данным модификатором является неполной или отсутствует. Модификатор abstract может использоваться с классами, методами, свойствами, индексаторами и событиями. Модификатор abstract в объявлении класса указывает, что класс предназначен только для использования в качестве базового класса для других классов. Члены, помеченные как абстрактные или включенные в абстрактный класс, должны быть реализованы с помощью классов, производных от абстрактных классов.
Абстрактные классы в действии
Итак, попробуем создать абстрактный класс:
using System;
namespace InheritanceAndPolymorphism
{
public abstract class ClassA
{
}
/// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
private static void Main(string[] args)
{
ClassA classA = new ClassA();
Console.ReadKey();
}
}
}
Попытаемся скомпилировать этот код:
Compile time error: Cannot create an instance of the abstract class or interface ‘InheritanceAndPolymorphism.ClassA’
Что нужно запомнить: Мы не можем создать экземпляр абстрактного класса с помощью ключевого слова new
.
Описание методов в абстрактном классе
Попробуем добавить в наш абстрактный класс немного кода:
/// <summary>
/// Abstract class ClassA
/// </summary>
public abstract class ClassA
{
public int a;
public void XXX()
{
}
}
/// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
private static void Main(string[] args)
{
ClassA classA = new ClassA();
Console.ReadKey();
}
}
С кодом класса никаких проблем нет, но скомпилировать снова не получается, потому что нельзя создавать экземпляры абстрактных классов.
Использование абстрактного класса в качестве базового
Давайте попробуем создать ещё один класс:
/// <summary>
/// Abstract class ClassA
/// </summary>
public abstract class ClassA
{
public int a;
public void XXX()
{
}
}
/// <summary>
/// Derived class.
/// Class derived from abstract class ClassA
/// </summary>
public class ClassB:ClassA
{
}
/// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
private static void Main(string[] args)
{
ClassB classB = new ClassB();
Console.ReadKey();
}
}
Вау. Теперь всё спокойно компилируется.
Что нужно запомнить: Мы можем унаследовать обычный класс от абстрактного.
Что нужно запомнить: Мы можем создать экземпляр обычного класса, унаследованного от абстрактного, с помощью ключевого слова new
.
Декларация методов в абстрактном классе
Теперь попробуем сделать вот так:
/// <summary>
/// Abstract class ClassA
/// </summary>
public abstract class ClassA
{
public int a;
public void XXX()
{
}
public void YYY();
}
/// <summary>
/// Derived class.
/// Class derived from abstract class ClassA.
/// </summary>
public class ClassB:ClassA
{
}
/// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
private static void Main(string[] args)
{
ClassB classB = new ClassB();
Console.ReadKey();
}
}
И… мы получаем ошибку компиляции:
Compile time error: ‘InheritanceAndPolymorphism.ClassA.YYY()’
must declare a body because it is not marked abstract, extern, or partial
Дело в том, что если мы объявляем метод в абстрактном классе и при этом хотим, чтобы его конкретное поведение было определено в производных классах, то к такому методу мы должны так же добавить ключевое слово abstract
. Добавим его:
/// <summary>
/// Abstract class ClassA
/// </summary>
public abstract class ClassA
{
public int a;
public void XXX()
{
}
abstract public void YYY();
}
/// <summary>
/// Derived class.
/// Class derived from abstract class ClassA.
/// </summary>
public class ClassB:ClassA
{
}
/// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
private static void Main(string[] args)
{
ClassB classB = new ClassB();
Console.ReadKey();
}
}
…и снова получим ошибку компиляции:
Compiler error: ‘InheritanceAndPolymorphism.ClassB’ does not implement
inherited abstract member ‘InheritanceAndPolymorphism.ClassA.YYY()’
Что нужно запомнить: Если мы хотим объявить метод в абстрактном классе, но не реализовывать его, к методу нужно добавить ключевое слово abstract
.
Что нужно запомнить: Если мы объявляем абстрактный метод в абстрактном классе, то этот метод должен реализовываться в неабстрактных наследниках этого класса.
Реализация абстрактного метода в производном классе
Так, давайте тогда попробуем реализовать метод YYY()
в классе ClassB
:
/// <summary>
/// Abstract class ClassA
/// </summary>
public abstract class ClassA
{
public int a;
public void XXX()
{
}
abstract public void YYY();
}
/// <summary>
/// Derived class.
/// Class derived from abstract class ClassA.
/// </summary>
public class ClassB:ClassA
{
public void YYY()
{
}
}
/// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
private static void Main(string[] args)
{
ClassB classB = new ClassB();
Console.ReadKey();
}
}
На первый взгляд всё отлично, правда? Но на этот раз мы получим сразу две ошибки компиляции:
Compile time error: ‘InheritanceAndPolymorphism.ClassB’ does not implement
inherited abstract member ‘InheritanceAndPolymorphism.ClassA.YYY()’Compile time warning: ‘InheritanceAndPolymorphism.ClassB.YYY()’ hides
inherited member ‘InheritanceAndPolymorphism.ClassA.YYY()’.
Дело в том, что в C# нужно явно объявить, что мы реализуем абстрактный метод класса-родителя с помощью ключевого слова override
:
/// <summary>
/// Abstract class ClassA
/// </summary>
public abstract class ClassA
{
public int a;
public void XXX()
{
}
abstract public void YYY();
}
/// <summary>
/// Derived class.
/// Class derived from abstract class ClassA.
/// </summary>
public class ClassB:ClassA
{
public override void YYY()
{
}
}
/// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
private static void Main(string[] args)
{
ClassB classB = new ClassB();
Console.ReadKey();
}
}
Ура! ^_^ У нас наконец-то нет никаких ошибок!
Абстрактный метод базового класса и метод с override класса-наследника должны быть одинаковы
Это значит, что мы не можем менять тип возвращаемого значения или аргументы, которые передаются в метод. Например, если мы напишем такое:
/// <summary>
/// Abstract class ClassA
/// </summary>
public abstract class ClassA
{
public int a;
public void XXX()
{
}
abstract public void YYY();
}
/// <summary>
/// Derived class.
/// Class derived from abstract class ClassA.
/// </summary>
public class ClassB:ClassA
{
public override int YYY()
{
}
}
/// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
private static void Main(string[] args)
{
ClassB classB = new ClassB();
Console.ReadKey();
}
}
То в консоли увидим следующую ошибку:
Compile time error: ‘InheritanceAndPolymorphism.ClassB.YYY()’: return type must be ‘void’
to match overridden member ‘InheritanceAndPolymorphism.ClassA.YYY()’
Инициализация переменных в абстрактных классах
В примерах выше, переменная int a
будет обладать значением по умолчанию (0). Мы можем изменить его на нужное нам прямо в абстрактном классе — с этим не связано никаких особенностей.
Абстрактные методы в неабстрактных классах
/// <summary>
/// Abstract class ClassA
/// </summary>
public class ClassA
{
public int a;
public void XXX()
{
}
abstract public void YYY();
}
/// <summary>
/// Derived class.
/// Class derived from abstract class ClassA.
/// </summary>
public class ClassB:ClassA
{
public override void YYY()
{
}
}
/// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
private static void Main(string[] args)
{
ClassB classB = new ClassB();
Console.ReadKey();
}
}
Такой код не скомпилируется:
Compiler error: ‘InheritanceAndPolymorphism.ClassA.YYY()’ is abstract
but it is contained in non-abstract class ‘InheritanceAndPolymorphism.ClassA’
Что нужно запомнить: Абстрактные методы могут быть объявлены только в абстрактных классах.
Вызов абстрактного метода родителя
/// <summary>
/// Abstract class ClassA
/// </summary>
public abstract class ClassA
{
public int a;
public void XXX()
{
}
abstract public void YYY();
}
/// <summary>
/// Derived class.
/// Class derived from abstract class ClassA.
/// </summary>
public class ClassB:ClassA
{
public override void YYY()
{
base.YYY();
}
}
/// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
private static void Main(string[] args)
{
ClassB classB = new ClassB();
Console.ReadKey();
}
}
Вывод:
Compile time error : Cannot call an abstract base member:
‘InheritanceAndPolymorphism.ClassA.YYY()’
Разумеется, мы не можем исполнить код, которого не существует.
Абстрактный класс, который наследуется от другого абстрактного класса
/// <summary>
/// Base class ClassA
/// </summary>
public class ClassA
{
public virtual void XXX()
{
Console.WriteLine("ClassA XXX");
}
}
/// <summary>
/// Derived abstract class.
/// Class derived from base class ClassA.
/// </summary>
public abstract class ClassB:ClassA
{
public new abstract void XXX();
}
public class ClassC:ClassB
{
public override void XXX()
{
System.Console.WriteLine("ClassC XXX");
}
}
/// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
private static void Main(string[] args)
{
ClassA classA = new ClassC();
ClassB classB = new ClassC();
classA.XXX(); classB.XXX();
}
}
Вывод:
ClassA XXX
ClassC XXX
Почему вывод именно такой, вы должны понимать из материалов третьей статьи этой серии.
Может ли абстрактный класс быть sealed
Проверим:
/// <summary>
/// sealed abstract class ClassA
/// </summary>
public sealed abstract class ClassA
{
public abstract void XXX()
{
Console.WriteLine("ClassA XXX");
}
}
/// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
private static void Main(string[] args)
{
}
}
И получим ошибку:
Compile time error: ‘InheritanceAndPolymorphism.ClassA’:
an abstract class cannot be sealed or static
Разумеется, абстрактный класс не может быть sealed
, т.к. он для того и создан, чтобы от него создавались производные классы.
Что нужно запомнить: Абстрактный класс не может иметь модификатор sealed
.
Что нужно запомнить: Абстрактный класс не может иметь модификатор static
.
Что мы узнали сегодня:
Что нужно запомнить
- Мы не можем создать экземпляр абстрактного класса с помощью ключевого слова
new
; - Мы можем унаследовать обычный класс от абстрактного;
- Мы можем создать экземпляр обычного класса, унаследованного от абстрактного, с помощью ключевого слова
new
; - Если мы хотим объявить метод в абстрактном классе, но не реализовывать его, к методу нужно добавить ключевое слово
abstract
; - Если мы объявляем абстрактный метод в абстрактном классе, то этот метод должен реализовываться в неабстрактных наследниках этого класса;
- Абстрактные методы могут быть объявлены только в абстрактных классах;
- Абстрактный класс не может иметь модификатор
sealed
; - Абстрактный класс не может иметь модификатор
static
.
Источник: «Diving in OOP (Day 4): Polymorphism and Inheritance (All About Abstract Classes in C#)»