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

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

public class Shape
{
   public virtual void Draw()
   {
      Console.WriteLine("Drawing a shape...");
   }
}

public class Circle : Shape
{
   public override void Draw()
   {
      Console.WriteLine("Drawing a circle...");
   }
}

public class Rectangle : Shape
{
   public override void Draw()
   {
      Console.WriteLine("Drawing a rectangle...");
   }
}

Shape shape1 = new Circle();
Shape shape2 = new Rectangle();

shape1.Draw(); // Outputs "Drawing a circle..."
shape2.Draw(); // Outputs "Drawing a rectangle..."

В этом примере кода у нас есть базовый класс Shape с виртуальным методом Draw(), который переопределен в производных классах Circle и Rectangle. Когда мы создаем экземпляры производных классов и вызываем метод Draw(), выполняются соответствующие переопределенные реализации. Это демонстрирует силу переопределения метода в достижении полиморфизма.

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

public class Calculator
{
   public int Add(int a, int b)
   {
      return a + b;
   }

   public double Add(double a, double b)
   {
      return a + b;
   }
}

var calculator = new Calculator();
int sum1 = calculator.Add(2, 3);          // Invokes the int overload
double sum2 = calculator.Add(2.5, 3.7);   // Invokes the double overload

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