// Что?! Понять это непросто...
MiniVan newVan = myVan * yourVan;
Перегрузка операций обычно полезна только при построении атомарных типов данных. Векторы, матрицы, текст, точки, фигуры, множества и т.п. будут подходящими кандидатами на перегрузку операций, но люди, менеджеры, автомобили, подключения к базе данных и веб-страницы — нет. В качестве эмпирического правила запомните, что если перегруженная операция затрудняет понимание пользователем функциональности типа, то не перегружайте ее. Используйте такую возможность с умом.
Понятие специальных преобразований типов
Давайте теперь обратимся к теме, тесно связанной с перегрузкой операций, а именно — к специальным преобразованиям типов. Чтобы заложить фундамент для последующего обсуждения, кратко вспомним понятие явных и неявных преобразований между числовыми данными и связанными типами классов.
Повторение: числовые преобразования
В терминах встроенных числовых типов (
sbyte
,
int
,
float
и т.д.)
явное преобразование требуется, когда вы пытаетесь сохранить большее значение в контейнере меньшего размера, т.к. подобное действие может привести к утере данных. По существу тем самым вы сообщаете компилятору, что отдаете себе отчет в том, что делаете. И наоборот —
неявное преобразование происходит автоматически, когда вы пытаетесь поместить меньший тип в больший целевой тип, что не должно вызвать потерю данных:
int a = 123;
long b = a; // Неявное преобразование из int в long.
int c = (int) b; // Явное преобразование из long в int.
Повторение: преобразования между связанными типами классов
В главе 6 было показано, что типы классов могут быть связаны классическим наследованием (отношение "является"). В таком случае процесс преобразования C# позволяет осуществлять приведение вверх и вниз по иерархии классов. Например, производный класс всегда может быть неявно приведен к базовому классу. Тем не менее, если вы хотите сохранить объект базового класса в переменной производного класса, то должны выполнить явное приведение:
// Два связанных типа классов.
class Base{}
class Derived : Base{}
// Неявное приведение производного класса к базовому.
Base myBaseType;
myBaseType = new Derived();
// Для сохранения ссылки на базовый класс в переменной
// производного класса требуется явное преобразование.
Derived myDerivedType = (Derived)myBaseType;
Продемонстрированное явное приведение работает из-за того, что классы
Base
и
Derived
связаны классическим наследованием, а объект
myBaseType
создан как экземпляр
Derived
. Однако если
myBaseType
является экземпляром
Base
, тогда приведение вызывает генерацию исключения
InvalidCastException
. При наличии сомнений по поводу успешности приведения вы должны использовать ключевое слово
as
, как обсуждалось в главе 6. Ниже показан переделанный пример:
// Неявное приведение производного класса к базовому.
Base myBaseType2 = new();
// Сгенерируется исключение InvalidCastException :
// Derived myDerivedType2 = (Derived)myBaseType2 as Derived;
// Исключения нет, myDerivedType2 равен null:
Derived myDerivedType2 = myBaseType2 as Derived;
Но что если есть два типа классов в разных иерархиях без общего предка (кроме
System.Object
), которые требуют преобразований? Учитывая, что они не связаны классическим наследованием, типичные операции приведения здесь не помогут (и вдобавок компилятор сообщит об ошибке).
В качестве связанного замечания обратимся к типам значений (структурам). Предположим, что имеются две структуры с именами
Square
и
Rectangle
. Поскольку они не могут задействовать классическое наследование (т.к. запечатаны), не существует естественного способа выполнить приведение между этими по внешнему виду связанными типами.
Несмотря на то что в структурах можно было бы создать вспомогательные методы (наподобие
Rectangle.ToSquare()
), язык C# позволяет строить специальные процедуры преобразования, которые дают типам возможность реагировать на операцию приведения
()
. Следовательно, если корректно сконфигурировать структуры, тогда для явного преобразования между ними можно будет применять такой синтаксис:
// Преобразовать Rectangle в Square!
Rectangle rect = new Rectangle
{
Width = 3;
Height = 10;
}
Square sq = (Square)rect;
Создание специальных процедур преобразования
Начните с создания нового проекта консольного приложения по имени
CustomConversions
. В языке C# предусмотрены два ключевых слова,
explicit
и
implicit
, которые можно использовать для управления тем, как типы должны реагировать на попытку преобразования. Предположим, что есть следующие определения структур:
using System;
namespace CustomConversions
{
public struct Rectangle
{
public int Width {get; set;}
public int Height {get; set;}
public Rectangle(int w, int h)
{
Width = w;
Height = h;
}