Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Операции и приведения.docx
Скачиваний:
1
Добавлен:
01.07.2025
Размер:
200.2 Кб
Скачать

Приведение между классами

Рисунок 7.1  Пример иерархии классов

Пример Currency включает только класс, который преоб­разуется в float и обратно  т.е. в один из предопределен­ных типов. Однако нет необходимости всегда в этот процесс вовлекать простые типы данных. Абсолютно законным явля­ется определение приведений между экземплярами разных структур или классов, определенных вами. Однако при этом следует помнить о ряде ограничений:

  • нельзя определять приведение между классами, если один из них является наследником другого (приведе­ние такого рода, как вы увидите, уже существует);

  • приведение может быть определено внутри определе­ния как исходного типа, так и типа назначения.

Чтобы проиллюстрировать эти требования, предполо­жим, что есть иерархия классов, показанная на рис. 7.1.

Другими словами, классы С и D непрямо унаследованы от А. В этом случае единственными допустимыми пользователь­ским приведением между типами А, В, С и D будут приведения между классами С и D, потому что эти классы не наследуют друг друга. Код таких приведений может выглядеть следую­щим образом (предполагается, что они будут явными, как это обычно бывает при опреде­лении приведений между пользовательскими типами):

public static explicit operator D(C value)

{

// и т.д.

}

public static explicit operator C(D value)

{

// и т.д.

}

При определении каждой из этих операций приведения у вас есть выбор, куда их по­местить  внутрь определения класса С или же внутрь определения класса D, но никуда более. C# требует, чтобы определение приведения было помещено в определение либо исходного класса (или структуры), либо целевого. Побочным эффектом этого требования является невозможность определить приведение между двумя классами, не имея доступа на редактирование исходного кода хотя бы одного из них. И это разумно, поскольку подоб­ным образом предотвращается определение приведений к вашим классам от независимых разработчиков.

После того как определено некоторое приведение внутри одного класса, определить такое же приведение внутри другого класса уже нельзя. Очевидно, что должна существо­вать только одна версия приведения для каждого преобразования, иначе компилятор не будет знать, какую выбрать.

Приведение между базовым и производным классами

Чтобы увидеть, как работают такие приведения, начнем с рассмотрения случая, когда и исходный, и целевой тип относятся к ссылочным, и возьмем для этого два класса  MyBase и MyDerived, причем MyDerived будет прямым или непрямым наследником MyBase.

Сначала пойдем от MyDerived к MyBase; всегда можно (предполагая, что конструкторы доступны) записать так:

MyDerived derivedObject = new MyDerived();

MyBase baseCopy = derivedObject;

В этом случае выполняется неявное приведение MyDerived к MyBase. Это работает, по­скольку существует правило, что любой ссылке типа MyBase разрешено ссылаться на объ­екты класса MyBase либо на объекты-наследники MyBase. В объектно-ориентированном программировании экземпляры классов-наследников являются в полном смысле экземпля­рами базового класса, плюс имеют некоторые дополнения. Все функции и поля, опреде­ленные в базовом классе, определены также в классе-наследнике.

В качестве альтернативы можно записать следующим образом:

MyBase derivedObject = new MyDerived();

MyBase baseObject = new MyBase();

MyDerived derivedCopy1 = (MyDerived) derivedObject; // нормально

MyDerived derivedCopy2=(MyDerived) baseObject;//генерирует исключение

Такой код абсолютно допустим в C# (в смысле синтаксиса) и иллюстрирует приведение от типа базового класса к классу-наследнику. Однако последний оператор во время выпол­нения программы сгенерирует исключение. Поскольку ссылка базового класса в принципе может указывать на экземпляр класса-наследника, допускается, чтобы этот объект был на самом деле экземпляром класса-наследника, к которому выполняется приведение. И если это именно такой случай, то приведение пройдет успешно, и ссылка производного типа будет указывать на объект. Если же, однако, объект на самом деле не будет экземпляром производного типа (или типа, унаследованного от него), такое приведение завершится не­удачей и приведет к исключению.

Обратите внимание, что приведения, предлагаемые компилятором, которые преобра­зуют базовый класс в производный, на самом деле не осуществляют никакого преобразо­вания данных. Все, что они делают  просто устанавливают ссылку на объект, если данное преобразование законно. В этом смысле природа таких приведений весьма отличается от тех, что вы определяете сами. Так, в представленном выше примере SimpleCurrency были определены приведения, трансформирующие данные из структуры Currency во встроенный тип float и обратно. В случае приведения float в Currency на самом деле создавался новый экземпляр структуры Currency, который инициализировался нужными значениями. Предопределенное приведение между базовым и производным классом этого не делает. Если вы действительно хотите преобразовать экземпляр MyBase в настоящий объект MyDerived, со значениями, базирующимися на содержимом экземпляра MyBase, вам не обязательно использовать для этого синтаксис приведения. Самым разумным будет в классе-наследнике определить конструктор, принимающий экземпляр базового класса в качестве параметра, и поручить ему выполнение соответствующей инициализации:

class DerivedClass : BaseClass

{

public DerivedClass(BaseClass rhs)

{

// Инициализация объекта по базовому экземпляру

}

// и т.д.