Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен
Шрифт:
Интервал:
Закладка:
// Перегрузка бинарных операций автоматически обеспечивает
// перегрузку сокращенных операций.
...
// Автоматически перегруженная операция +=
Point ptThree = new Point(90, 5);
Console.WriteLine("ptThree = {0}", ptThree);
Console.WriteLine("ptThree += ptTwo: {0}", ptThree += ptTwo);
// Автоматически перегруженная операция -=
Point ptFour = new Point(0, 500);
Console.WriteLine("ptFour = {0}", ptFour);
Console.WriteLine("ptFour -= ptThree: {0}", ptFour -= ptThree);
Console.ReadLine();
Перегрузка унарных операций
В языке C# также разрешено перегружать унарные операции, такие как ++ и --. При перегрузке унарной операции также должно использоваться ключевое слово static с ключевым словом operator, но в этом случае просто передается единственный параметр того же типа, что и класс или структура, где операция определена. Например, дополните реализацию Point следующими перегруженными операциями:
public class Point
{
...
// Добавить 1 к значениям X/Y входного объекта Point.
public static Point operator ++(Point p1)
=> new Point(p1.X+1, p1.Y+1);
// Вычесть 1 из значений X/Y входного объекта Point.
public static Point operator --(Point p1)
=> new Point(p1.X-1, p1.Y-1);
}
В результате появляется возможность инкрементировать и декрементировать значения X и Y класса Point:
...
// Применение унарных операций ++ и -- к объекту Point.
Point ptFive = new Point(1, 1);
Console.WriteLine("++ptFive = {0}", ++ptFive); // [2, 2]
Console.WriteLine("--ptFive = {0}", --ptFive); // [1, 1]
// Применение тех же операций в виде постфиксного инкремента/декремента.
Point ptSix = new Point(20, 20);
Console.WriteLine("ptSix++ = {0}", ptSix++); // [20, 20]
Console.WriteLine("ptSix-- = {0}", ptSix--); // [21, 21]
Console.ReadLine();
В предыдущем примере кода специальные операции ++ и -- применяются двумя разными способами. В языке C++ допускается перегружать операции префиксного и постфиксного инкремента/декремента по отдельности. В C# это невозможно. Однако возвращаемое значение инкремента/декремента автоматически обрабатывается корректно (т.е. для перегруженной операции ++ выражение pt++ дает значение неизмененного объекта, в то время как результатом ++pt будет новое значение, устанавливаемое перед использованием в выражении).
Перегрузка операций эквивалентности
Как упоминалось в главе 6, метод System.Object.Equals() может быть перегружен для выполнения сравнений на основе значений (а не ссылок) между ссылочными типами. Если вы решили переопределить Equals() (часто вместе со связанным методом System.Object.GetHashCode()), то легко переопределите и операции проверки эквивалентности (== и !=). Взгляните на обновленный тип Point:
// В данной версии типа Point также перегружены операции == и !=.
public class Point
{
...
public override bool Equals(object o)
=> o.ToString() == this.ToString();
public override int GetHashCode()
=> this.ToString().GetHashCode();
// Теперь перегрузить операции == и !=.
public static bool operator ==(Point p1, Point p2)
=> p1.Equals(p2);
public static bool operator !=(Point p1, Point p2)
=> !p1.Equals(p2);
}
Обратите внимание, что для выполнения всей работы в реализациях операций == и != просто вызывается перегруженный метод Equals(). Вот как теперь можно применять класс Point:
// Использование перегруженных операций эквивалентности.
...
Console.WriteLine("ptOne == ptTwo : {0}", ptOne == ptTwo);
Console.WriteLine("ptOne != ptTwo : {0}", ptOne != ptTwo);
Console.ReadLine();
Как видите, сравнение двух объектов с использованием хорошо знакомых операций == и != выглядит намного интуитивно понятнее, чем вызов метода Object.Equals(). При перегрузке операций эквивалентности для определенного класса имейте в виду, что C# требует, чтобы в случае перегрузки операции == обязательно перегружалась также и операция != (компилятор напомнит, если вы забудете это сделать).
Перегрузка операций сравнения
В главе 8 было показано, каким образом реализовывать интерфейс IComparable для сравнения двух похожих объектов. В действительности для того же самого класса можно также перегрузить операции сравнения (<, >, <= и >=). Как и в случае операций эквивалентности, язык C# требует, чтобы при перегрузке операции < обязательно перегружалась также операция >. Если класс Point перегружает указанные операции сравнения, тогда пользователь объекта может сравнивать объекты Point:
// Использование перегруженных операций < и >.
...
Console.WriteLine("ptOne < ptTwo : {0}", ptOne < ptTwo);
Console.WriteLine("ptOne > ptTwo : {0}", ptOne > ptTwo);
Console.ReadLine();
Когда интерфейс IComparable (или, что еще лучше, его обобщенный эквивалент) реализован, перегрузка операций сравнения становится тривиальной. Вот модифицированное определение класса:
// Объекты Point также можно сравнивать посредством операций сравнения.
public class Point : IComparable<Point>
{
...
public int CompareTo(Point other)
{
if (this.X > other.X && this.Y > other.Y)
{
return 1;
}
if (this.X < other.X && this.Y < other.Y)
{
return -1;
}
return 0;
}
public static bool operator <(Point p1, Point p2)
=> p1.CompareTo(p2) < 0;
public static bool operator >(Point p1, Point p2)
=> p1.CompareTo(p2) > 0;
public static bool operator <=(Point p1, Point p2)
=> p1.CompareTo(p2) <= 0;
public static bool operator >=(Point p1, Point p2)
=> p1.CompareTo(p2) >= 0;
}
Финальные соображения относительно перегрузки операций
Как уже упоминалось, язык C# предоставляет возможность построения типов, которые могут уникальным образом реагировать на разнообразные встроенные хорошо известные операции. Перед добавлением поддержки такого поведения в классы вы должны удостовериться в том,