0Pricing
C# Academy · Урок

Рекомендации по перегрузке операторов

Сохраняйте интуитивность перегруженных операторов.

«Рекомендации по перегрузке операторов» — бесплатный урок C# Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C# Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C# Academy содержит 4 уроков всего.

Рекомендации по перегрузке операторов

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

Перегружайте только при очевидном смысле

Перегружайте оператор только тогда, когда его смысл для вашего типа однозначен. + для вектора явно означает покомпонентное сложение, а + для Order лишь запутает читателей.

using System;

public struct Vector2
{
    public int X, Y;
    public Vector2(int x, int y) { X = x; Y = y; }
    // + means adding components: obvious and expected
    public static Vector2 operator +(Vector2 a, Vector2 b)
        => new Vector2(a.X + b.X, a.Y + b.Y);
    public override string ToString() => "(" + X + ", " + Y + ")";
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine(new Vector2(1, 2) + new Vector2(3, 4));
    }
}

Сохраняйте математическую интуицию

Операторы должны соответствовать интуиции: a + b должно давать тот же результат, что и b + a, когда сложение коммутативно, а + не должен незаметно выполнять вычитание. Такие сюрпризы приводят к трудноуловимым ошибкам.

using System;

public struct Money
{
    public decimal Amount;
    public Money(decimal a) { Amount = a; }
    public static Money operator +(Money a, Money b) => new Money(a.Amount + b.Amount);
    public override string ToString() => "$" + Amount;
}

public class Program
{
    public static void Main()
    {
        var x = new Money(3);
        var y = new Money(5);
        Console.WriteLine((x + y).Amount == (y + x).Amount); // commutative
    }
}

Сохраняйте симметрию и парность

Язык объединяет некоторые операторы в пары, но вам также следует сохранять логическую симметрию: если вы добавляете +, рассмотрите и -; если добавляете <, также предоставьте > и варианты с равенством. Незавершённые наборы операторов раздражают пользователей.

using System;

public struct Length
{
    public int Mm;
    public Length(int mm) { Mm = mm; }
    public static Length operator +(Length a, Length b) => new Length(a.Mm + b.Mm);
    public static Length operator -(Length a, Length b) => new Length(a.Mm - b.Mm);
    public override string ToString() => Mm + "mm";
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine(new Length(50) + new Length(20));
        Console.WriteLine(new Length(50) - new Length(20));
    }
}

Не изменяйте операнды

Операторы должны быть чистыми: вычисляйте и возвращайте новое значение, не изменяя операнды. Изменение операнда внутри + нарушает все ожидания читателя.

using System;

public struct Vector2
{
    public int X, Y;
    public Vector2(int x, int y) { X = x; Y = y; }
    public static Vector2 operator +(Vector2 a, Vector2 b)
        => new Vector2(a.X + b.X, a.Y + b.Y); // returns new, mutates nothing
    public override string ToString() => "(" + X + ", " + Y + ")";
}

public class Program
{
    public static void Main()
    {
        var a = new Vector2(1, 1);
        var unused = a + new Vector2(9, 9);
        Console.WriteLine("a is still " + a);
    }
}

Согласуйте == с Equals

Важная рекомендация: при перегрузке == переопределяйте Equals и GetHashCode, чтобы все способы проверки равенства давали согласованный результат. Несогласованность здесь — одна из самых распространённых ошибок при работе с операторами.

using System;

public struct Point
{
    public int X, Y;
    public Point(int x, int y) { X = x; Y = y; }
    public override bool Equals(object obj) => obj is Point p && p.X == X && p.Y == Y;
    public override int GetHashCode() => HashCode.Combine(X, Y);
    public static bool operator ==(Point a, Point b) => a.Equals(b);
    public static bool operator !=(Point a, Point b) => !a.Equals(b);
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine(new Point(1, 1) == new Point(1, 1));
    }
}

Предоставляйте именованные альтернативы

Некоторые языки и инструменты анализа не умеют использовать операторы. Именованный метод, например Add наряду с +, улучшает совместимость и упрощает обнаружение нужной возможности.

using System;

public struct Vector2
{
    public int X, Y;
    public Vector2(int x, int y) { X = x; Y = y; }
    public Vector2 Add(Vector2 other) => new Vector2(X + other.X, Y + other.Y);
    public static Vector2 operator +(Vector2 a, Vector2 b) => a.Add(b);
    public override string ToString() => "(" + X + ", " + Y + ")";
}

public class Program
{
    public static void Main()
    {
        var v = new Vector2(1, 2);
        Console.WriteLine(v.Add(new Vector2(3, 4)));
        Console.WriteLine(v + new Vector2(3, 4));
    }
}

Предпочитайте операторы типам, подобным значениям

Операторы естественно выглядят у неизменяемых типов, подобных значениям (структур или классов, подобных записям). Для изменяемых типов, где важна идентичность, именованные методы обычно лучше передают намерение.

using System;

public readonly struct Rational
{
    public readonly int Num, Den;
    public Rational(int n, int d) { Num = n; Den = d; }
    public static Rational operator *(Rational a, Rational b)
        => new Rational(a.Num * b.Num, a.Den * b.Den);
    public override string ToString() => Num + "/" + Den;
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine(new Rational(1, 2) * new Rational(2, 3));
    }
}

Документируйте особые случаи

Явно указывайте, как операторы обрабатывают особые значения: деление на ноль, операнды со значением null у классов или переполнение. Предсказуемая обработка не позволяет застать вызывающий код врасплох.

using System;

public struct SafeDiv
{
    public int Value;
    public SafeDiv(int v) { Value = v; }
    // Documented behavior: dividing by zero yields zero
    public static SafeDiv operator /(SafeDiv a, SafeDiv b)
        => new SafeDiv(b.Value == 0 ? 0 : a.Value / b.Value);
    public override string ToString() => Value.ToString();
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine(new SafeDiv(10) / new SafeDiv(0));
        Console.WriteLine(new SafeDiv(10) / new SafeDiv(2));
    }
}

Продуманный набор операторов

Отточенный числовой тип сочетает несколько интуитивных операторов, согласованное равенство и неизменяемость. В результате он ведёт себя как встроенное число и приносит удовольствие в использовании.

using System;

public readonly struct Vec
{
    public readonly double X, Y;
    public Vec(double x, double y) { X = x; Y = y; }
    public static Vec operator +(Vec a, Vec b) => new Vec(a.X + b.X, a.Y + b.Y);
    public static Vec operator -(Vec a, Vec b) => new Vec(a.X - b.X, a.Y - b.Y);
    public static Vec operator *(Vec a, double k) => new Vec(a.X * k, a.Y * k);
    public override string ToString() => "(" + X + ", " + Y + ")";
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine((new Vec(1, 2) + new Vec(3, 4)) * 2);
    }
}

Попробуйте сами

Объедините рекомендации на практике: создайте неизменяемый тип-значение, интуитивные операторы, именованную альтернативу и согласованное равенство.

using System;

public readonly struct Meters : IEquatable<Meters>
{
    public readonly double Value;
    public Meters(double v) { Value = v; }
    public Meters Add(Meters other) => new Meters(Value + other.Value);
    public static Meters operator +(Meters a, Meters b) => a.Add(b);
    public bool Equals(Meters other) => Value == other.Value;
    public override bool Equals(object obj) => obj is Meters m && Equals(m);
    public override int GetHashCode() => Value.GetHashCode();
    public static bool operator ==(Meters a, Meters b) => a.Equals(b);
    public static bool operator !=(Meters a, Meters b) => !a.Equals(b);
    public override string ToString() => Value + "m";
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine(new Meters(3) + new Meters(4));
        Console.WriteLine(new Meters(7) == new Meters(7));
    }
}

Быстрая проверка

Примените рекомендации по перегрузке операторов.

Итоги

Хорошая перегрузка операторов отличается продуманностью и предсказуемостью.

  • Перегружайте операторы только при очевидном смысле.
  • Сохраняйте математическую интуицию и симметрию.
  • Оставайтесь чистыми: возвращайте новые значения и никогда не изменяйте операнды.
  • Согласуйте == с Equals/GetHashCode.
  • Предлагайте именованные альтернативы и предпочитайте типы, подобные значениям.

Часто задаваемые вопросы

Урок «Рекомендации по перегрузке операторов» бесплатный?

Да — полный текст урока «Рекомендации по перегрузке операторов» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C# Academy, подпишись на CoddyKit PRO. Курс C# Academy содержит 4 уроков всего.

Чему я научусь в уроке «Рекомендации по перегрузке операторов»?

Сохраняйте интуитивность перегруженных операторов. Ты практикуешь C# Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать C# Academy?

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

Сколько времени занимает урок «Рекомендации по перегрузке операторов»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке C# Academy?

Да. Каждый урок C# Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Перегрузка арифметических операторов
  2. Перегрузка операторов сравнения
  3. Пользовательские преобразования
  4. Рекомендации по перегрузке операторов
← Назад к C# Academy