В объектно-ориентированном программировании, вероятно, наиболее частым приведением типа является приведение объекта к другому типу.
Восходящие преобразования (Up-casts)
Этот вид приведения типа вообще-то не нужен, потому что компилятор неявно делает его, конвертируя объект от дочернего класса к базовому (родительскому). Это выполняется каждый раз, когда вы вызываете унаследованный метод, передаёте значения в параметры типаTObject
, добавляете объекты в TObjectList
и т.д. Хотя компилятор неявно обработает такие приведения типов, он позволяет программисту выполнять их явно, но они оптимизируются в пустую операцию:
type TParent = class end; TChild = class(TParent) end; var Parent: TParent; Child: TChild; begin Child := TChild.Create; Parent := Child; // Неявное преобразование Parent := TParent(Child); // Явноё жёсткое преобразование Parent := Child as TParent; // Явное преобразование через as if Child is TParent then // is-тест Writeln('Yup'); end.Этот код демонстрирует синтаксис, используемый для жёстких преобразований типа, проверяемые в run-time приведения типов через as и is-проверки. Компилятор Win32 предполагает, что вы не пытаетесь насильно нарушить работу системы типов, поэтому он компилирует первые преобразования типов в пустую операцию, а is-проверку - в проверку
Assigned
.Нисходящие преобразования (Down-casts)
Это означает, что если вы играете грязно и используете жёсткое преобразование для хранения, скажем,TObject
в переменной TChild
(нарушая систему типов), то преобразования и is-проверка пройдут успешно:
var O: TObject; // ... Child := TChild(TObject.Create); // Грязный трюк - никогда так не делайте if Child is TParent then // is-проверка Writeln('Fake!'); O := Child; if not (O is TParent) then // is-проверка Writeln('Nope!');.NET делает практически невозможным обойти систему типов (до уровня небезопасного и неуправляемого кода, конечно же). Хотя as-преобразования и is-проверки работают одинаково в Win32 и .NET, жёсткие преобразования типов в .NET вернут
nil
для неверных преобразований и нарушат систему типов в Win32. Так что код выше, будучи скомпилирован в .NET, на самом деле сохранит в переменной Child nil
(потому что TObject
- это не TChild
). Нисходящее преобразование - это преобразование от родительского класса к дочернему. Именно его мы обычно имеем ввиду, когда говорим о преобразовании типа объекта.Безопасные и небезопасные преобразования типов
В Win32 жёсткие преобразования типов небезопасны, потому что компилятор не предпринимает ничего, даже если вы попытаетесь выполнить заведомо недопустимое/"невозможное" преобразование. Жёсткие преобразования типов - это типа как способ сказать компилятору Win32:"Расслабься, я знаю, что я делаю. Просто закрой глаза и интерпретируй эти биты, как я тебе говорю".Поэтому при этом у вас не будет никаких проверок или преобразований (правда, здесь есть несколько исключений из правила - о них в другой раз). В .NET же даже жёсткие преобразования типов являются безопасными, потому что компилятор и run-time будут проверять, что это преобразования допустимо. Для преобразований типа объекта, CLR проверит, что источник совместим с целевым типом - если нет, то преобразование вернёт
nil
. Концептуально, жёсткое преобразование типа в .NET делает следующее:
Target := TTargetClass(Source); // Приводит к: if Source is TTargetClass then Target := Source else Target := nil;As-преобразования безопасны на обоих платформах. Если преобразование будет недопустимым, то будет возбуждено исключение:
Target := Source as TTargetClass; // Приводит к: if Source is TTargetClass then Target := Source else raise EInvalidCast.Create;
Вопросы производительности
В Win32 существуют небольшие накладные расходы на is- и as-проверки - потому что компилятору и RTL нужно пройтись по дереву наследования, чтобы проверить соответствие класса. В большинстве случаев это очень незначительная добавка, которую можно игнорировать. Часто вам требуется сконвертировать объектную ссылку в конкретный тип, но вы хотите избежать исключения, поэтому вы пишете:if Instance is TMyClass then MyObject := Instance as TMyClass;Эта конструкция часто не нравится некоторым программистам Delphi - они указывают на то, что as повторно выполняет проверку, которая уже была сделана is. В большинстве случаев это не имеет никакого значения, но если вы хотите, то можете переписать этот код так:
if Instance is TMyClass then begin MyObject := TMyClass (Instance); // ... xxxЗдесь выполняется безопасная проверка is, за которой следует жёсткое (небезопасное) преобразование. Но будучи соединённым с is-проверкой, жёсткое преобразование становится безопасным! В Win32 этот код выполнит только одну проверку наследования. Однако в .NET этот же код выполняет две проверки - потому что жёсткое преобразование типов в .NET скрывает под собой ещё одну is-проверку (возвращая
nil
для недопустимого преобразования). Поэтому маньяки производительности могут захотеть переписать этот код так:
MyObject := TMyClass(Instance); if Assigned(MyObject) then begin // ...Это будет отлично работать без лишних накладных расходов в .NET, но будет вылетать в Win32, если это преобразование неудачно. Примеры выше отлично справляются со случаем, когда преобразование завершается неудачно в run-time. В каких-то других ситуациях вы можете посчитать неудачу преобразования ошибкой программиста. Тогда вы обычно используете преобразование as - оно возбудит исключение
EInvalidCast
(псевдоним для System.InvalidCastException
в .NET) в случае неудачи. К примеру, это частая вещь в обработчиках событий. Параметр Sender имеет общий тип TObject
, так что вам может понадобится преобразовать его во что-то более удобоваримое (скажем, TDrawGrid
). Это также удобно, когда вы разделяете один обработчик событий между несколькими компонентами. Одним способом сделать это является:
procedure TMyForm.GridsDrawCell(Sender: TObject; ...); var Grid: TDrawGrid; begin Grid := Sender as TDrawGrid; // ... end;Этот код делает as-преобразование для каждой операции рисования ячейки для всех таблиц на форме. Но единственным случаем, когда что-то может пойти не так, является случай, когда вы по ошибке назначаете этот обработчик не DrawGrid-у. Зачем выпускать приложение с проверками, которые обречены на успех? Поэтому вы можете захотеть сменить этот код на:
procedure TMyForm.GridsDrawCell(Sender: TObject; ...); var Grid: TDrawGrid; begin Assert(Sender is TDrawGrid); Grid := TDrawGrid(Sender); // ... end;Теперь в release-версиях проверка в run-time уйдёт (кроме .NET и случаев, когда вы вручную включаете опцию для Assert). Ошибки глупого программиста всё ещё будут пойманы в отладочных сборках (где проверка работает). Будьте осторожны, чтобы не применить эту оптимизацию сверх меры. Вы захотите оставить as и is в коде, где тип объекта может варьироваться.
Трюк с доступом к секции protected
Я говорил о нём ранее. Он основан на выполнении некорректного жёсткого преобразования типов с определённым локально хак-классом. Это делается для получения доступа к protected-секции объекта, например:
type TControlAccess = class(TControl); begin TControlAccess(MyControl).Click; end;Click - это
protected
метод TControl
, поэтому обычно вы не можете вызвать его для экземпляра типа TControl
. Чтобы обмануть систему типов, мы объявляем пустой локальный класс, который наследуется от TControl. Из-за того, что наш код расположен в том же модуле, мы получаем доступ ко всем protected
членам класса. Хотя MyControl
не является в действительности экземпляром TControlAccess
, мы преобразовываем его тип к нему, чтобы вызвать метод Click
. Это работает в Win32, но является хаком.Этот хак настолько частый, что многие VCL-компоненты и Delphi-приложения зависят от него. Borland даже решила поддержать его и в .NET. Это сработает в .NET пока члены, к которым получают доступ, расположены в той же сборке, что и вызывающий код. Компилятор отмечает все protected-члены класса маркером доступа CLR
protected-or-assembly
. Это означает, что на уровне IL все protected члены доступны напрямую всему коду в той же сборке.Но компилятор Delphi не разрешает доступ к этим членам, пока вы не выполните этот трюк с преобразованием. Когда компилятор встречает преобразование, он просто удаляет его - в IL коде нет никакого следа преобразования. CLR позволит коду получить доступ к
protected
членам, если только это будет ссылка из той же сборки. Конечно же, компилятор Delphi также не даст вам сделать это между сборками. К примеру, если вы попробуете скомпилировать код выше, ссылаясь (referencing), а не прилинковывая (linking in) сборку Delphi.Vcl.dll
, то компилятор выдаст такую ошибку компиляции:
[Error] Cross-assembly protected reference to [Borland.Vcl]TControl.Click in Upcasts.Upcasts
Комментариев нет:
Отправить комментарий
Можно использовать некоторые HTML-теги, например:
<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>
Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.
Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.
Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.
Примечание. Отправлять комментарии могут только участники этого блога.