Что такое ad-hoc полиморфизм?

Ответ

Ad-hoc полиморфизм (полиморфизм специальных случаев) — это способность функции или оператора иметь разные реализации для разных типов аргументов, определяемые на этапе компиляции. В C# он реализуется через перегрузку методов (method overloading) и перегрузку операторов (operator overloading).

Ключевое отличие от параметрического полиморфизма (generics):

  • Ad-hoc: Поведение метода разное для каждого типа (int, string). Компилятор выбирает конкретную реализацию на основе типов аргументов.
  • Generics: Поведение метода одинаковое (универсальное) для всех подставляемых типов.

Пример перегрузки методов:

public class Logger
{
    // Разные реализации для разных типов входных данных
    public void Log(int number)
    {
        Console.WriteLine($"[INFO] Integer: {number}");
    }

    public void Log(string message)
    {
        Console.WriteLine($"[INFO] String: {message}");
    }

    public void Log(string message, LogLevel level) // Перегрузка по количеству параметров
    {
        Console.WriteLine($"[{level}] {message}");
    }
}

// Использование
var logger = new Logger();
logger.Log(42);               // Вызовет Log(int)
logger.Log("Hello");          // Вызовет Log(string)
logger.Log("Error!", LogLevel.Error); // Вызовет Log(string, LogLevel)

Пример перегрузки операторов (ad-hoc полиморфизм для операторов):

public struct Vector2D
{
    public double X { get; }
    public double Y { get; }

    public Vector2D(double x, double y) { X = x; Y = y; }

    // Определяем поведение оператора '+' для нашего типа
    public static Vector2D operator +(Vector2D a, Vector2D b)
    {
        return new Vector2D(a.X + b.X, a.Y + b.Y);
    }
}

var v1 = new Vector2D(1, 2);
var v2 = new Vector2D(3, 4);
var sum = v1 + v2; // Будет использована наша перегруженная реализация оператора

Практическая ценность: Ad-hoc полиморфизм повышает читаемость кода, позволяя использовать одно и то же осмысленное имя метода (Log, Calculate, Draw) для операций над разными типами данных.

Ответ 18+ 🔞

А, слушай, вот есть такая штука — ad-hoc полиморфизм, или, по-нашему, полиморфизм специальных случаев. Это когда функция или оператор ведут себя по-разному для разных типов данных, и компилятор ещё на этапе сборки решает, какую именно реализацию выбрать. В C# это делается через перегрузку методов и перегрузку операторов.

Чем отличается от generics (параметрического полиморфизма)?

  • Ad-hoc: Для каждого типа — своя, отдельная реализация. Для int — одна логика, для string — другая. Компилятор смотрит на типы аргументов и тыкает пальцем: "Ага, вот эту функцию берём".
  • Generics: Логика одна и та же, универсальная, просто тип подставляется как параметр. Как шаблон, блядь.

Пример перегрузки методов:

public class Logger
{
    // Для целых чисел — одна история
    public void Log(int number)
    {
        Console.WriteLine($"[INFO] Integer: {number}");
    }

    // Для строк — уже другая
    public void Log(string message)
    {
        Console.WriteLine($"[INFO] String: {message}");
    }

    // А тут ещё и по количеству параметров перегрузили
    public void Log(string message, LogLevel level)
    {
        Console.WriteLine($"[{level}] {message}");
    }
}

// Использование
var logger = new Logger();
logger.Log(42);               // Вызовет Log(int) — работает с числами
logger.Log("Hello");          // Вызовет Log(string) — работает со строками
logger.Log("Error!", LogLevel.Error); // Вызовет Log(string, LogLevel) — третий вариант

Пример перегрузки операторов (это тоже ad-hoc полиморфизм, ёпта):

public struct Vector2D
{
    public double X { get; }
    public double Y { get; }

    public Vector2D(double x, double y) { X = x; Y = y; }

    // Говорим компилятору: "Слышь, для нашего типа Vector2D оператор '+' работает вот так"
    public static Vector2D operator +(Vector2D a, Vector2D b)
    {
        return new Vector2D(a.X + b.X, a.Y + b.Y);
    }
}

var v1 = new Vector2D(1, 2);
var v2 = new Vector2D(3, 4);
var sum = v1 + v2; // Используется наша, кастомная реализация, а не стандартная

В чём профит, спросишь? Читаемость, блядь! Вместо того чтобы плодить методы вроде LogInteger, LogString, LogStringWithLevel, можно использовать одно осмысленное имя — Log. И все сразу понимают, что происходит, даже не вдаваясь в детали. Код становится чище, а жизнь — немного светлее, хоть и ненадолго.