Чтение онлайн

ЖАНРЫ

Язык программирования C#9 и платформа .NET5
Шрифт:

Например, пусть перегружена операция умножения для класса

MiniVan
, представляющего минивэн. Что по своей сути будет означать перемножение двух объектов
MiniVan
? В нем нет особого смысла. На самом деле коллеги по команде даже могут быть озадачены, когда увидят следующее применение класса
MiniVan
:

// Что?! Понять это непросто...

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;

}

public void Draw

{

for (int i = 0; i < Height; i++)

{

for (int j = 0; j < Width; j++)

{

Console.Write("*");

}

Console.WriteLine;

}

}

public override string ToString

=> $"[Width = {Width}; Height = {Height}]";

}

}

using System;

namespace CustomConversions

{

public struct Square

{

public int Length {get; set;}

public Square(int l) : this

{

Length = l;

}

public void Draw

{

for (int i = 0; i < Length; i++)

Поделиться с друзьями: