![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
Операции с классами [D7, XP]
Nickolay © (2010-12-08 09:27:00)Здравствуйте!
Кто может объяснить следующую особенность операции as (Obj as TObj)?
В справке говорится, что она возвращает ссылку на объект Obj как на
объект уже класса TObj. Причём Obj должен быть из TObj или быть его потомком.
Короче, as приводит Obj к нужному типу TObj.
Я завожу два класса - предок TA и потомок TB. Переобъявляю в TB поле,
перекрываю статический метод. Затем преобразую тип объекта B из TB к TA.
Но тип не преобразовывается с помощью as! Функциональное преобразование
работает. Если правым операндом поставить переменную-ссылку на класс TA,
то и as работает. А вот если правый операнд - просто тип TA, то не работает!
Что это - ошибка в компиляторе?
type
TAClass = class of TA;
TA = class // объявление класса-предка
public
Val: Integer;
procedure Met;
end;
TB = class(TA) // объявление класса-потомка
public
Val: string; // теперь поле предка скрыто
procedure Met;
end;
procedure TA.Met;
begin
WriteLn('Это TA.Met')
end;
procedure TB.Met;
begin
WriteLn('Это TB.Met')
end;
var
B: TB;
Aref: TAClass;
begin
Aref := TA; // переменная-ссылка на класс-предок
B := TB.Create; // B - экземпляр класса-потомка
B.Val := 'Ноябрь'; // работаем со своим полем
B.Met; // вызываем свой статический метод
TA(B).Val := 18; // работаем с полем предка
TA(B).Met; // вызываем метод предка
(B as Aref).Val := 18;// работаем с полем предка
(B as Aref).Met; // вызываем метод предка
(B as TA).Val := 18; // здесь операция as не работает - ошибка
(B as TA).Met; // здесь операция as не работает - вызов метода потомка
Медвежонок ХМЛ (2010-12-08 12:58:00) [21]Здесь as работает...
многое зависит от типа вызываемого метода.
если метод статический, то то, чей именно метод будет вызван - определяется типом переменной.
А если метод динамический, то типом объекта, на который содержится ссылка в переменной. Сам тип переменной при этом роли не играет.
Nickolay (2010-12-08 12:52:00) [22]
> Servy © (08.12.10 12:09) [15]
1) Там кусок кода приведен для демонстрации механизмов работы Делфи, а не в качестве "делайте вот так".
2) Если бы B был объявлен базового типа TObject, то тут еще бабка надвое сказала, совместим ли он с TA. Посему, если в вашем коде из [0] изменить объявление переменной B:
var
B: TObject;
1) А я никогда не утверждал и не утверждаю, что "надо делать так".
Я просто показываю, что данный "механизм Дельфи", похоже, работает не всегда.
2) Зачем TObject? Я просто поменяю ролями TA и TB, только предка надо будет инициализировать как потомка:
var
A: TA;
begin
A := TB.Create; // A - экземпляр всё-таки класса-потомка
TB(A).Val := 'Ноябрь'; // работаем с полем потомка
TB(A).Met; // вызываем метод потомка
(A as TB).Val := 'Ноябрь'; // работаем с полем потомка
(A as TB).Met; // вызываем метод потомка
Здесь as работает...
Servy (2010-12-08 12:33:00) [23]Попробую еще уточнить. as обычно используется так:
TA = class
end;
TB = class(TA)
public
procedure Smth;
end;
var
A: TA;
begin
Randomize;
if Random(2) = 0 then
A := TA.Create
else
A := TB.Create;
// нам нужно получить доступ к методу TB;
// если рандом создал нам экземпляр TA - словим Exception - на то и проверяемой приведение типов
(A as TB).Smth;
Вы же используете его наоборот:
TA = class
public
procedure SmthElse;
end;
TB = class(TA)
public
procedure Smth;
end;
var
B: TB;
begin
Randomize;
// так мы сделать не может, ибо в переменную типа TB идут только классы - наследники TB
//if Random(2) = 0 then
// B := TA.Create
//else
// B := TB.Create;
B := TB.Create;
// приводим тип к TA и вызываем метод TA
(B as TA).SmthElse;
// однако B - и так наследник TA и имеет нужный метод, так что танцы с бубном не нужны, можно писать просто
// B.SmthElse;
// что собственно и делает компилятор.
В вашем же примере Smth и SmthElse имеют одно и то же имя, что приводит к обозначенной вами проблеме. Давать одинаковые имена полям или методам - плохая идея в целом, так как человек идентифицирует по имени сущность. И если вместо одной сущности Val у объекта окажется две сущности с таким именем, и где-то идет работа с одной, а где-то с другой, а мне бы пришлось потом поддерживать этот код, я бы на того, кто это написал сильно обиделся.
Если вы все еще считаете, что это баг - создайте тикет в QC. Я не вижу в наблюдаемом поведении проблемы.
Servy (2010-12-08 12:09:00) [24]
> 1) Для проверки принадлежности классу есть другая операция
> - is
Что не мешает компилятору работать так, как он работает, а мне - комментировать его работу так, как я ее понимаю. По-моему, ваше заявление не относится к сути вопроса. Возможно, вы не поняли мое сообщение или я его непонятно написал, попробую уточнить.
as - checked typecast. Проверяемое приведение типов. Если у нас имеется:
B: TB;
и констуркция
(B as TA)
, причем TA - предок TB, то проверка всегда вернет пройдет (всегда вернет true). Потому что в переменной B не должно лежать ничего, несовместимого с классом TB. А класс TB совместим с классом TA, ибо потомок - наследует все его свойства. Таким образом, в данном контексте, когда проверка возвращает всегда true, typecast не производится. Если бы B был объявлен базового типа TObject, то тут еще бабка надвое сказала, совместим ли он с TA. Посему, если в вашем коде из [0] изменить объявление переменной B:
var
B: TObject;
То ваш код будет таки работать (правда, часть где вы явно обращаетесь к B.Val придется по понятным причинам закомментировать). Таким образом:
> А вот "typecast", похоже, можно сделать только с помощью
> функционального преобразования.
неверно, as тоже вполне себе делает typecast. Она лишь не делает его лишь в случае, когда проверка совместимости типов совершенно не нужна. В большинстве ситуаций, когда нет скрытия видимости полей или методов потомком, такое поведение ничем не отличается от ожидаемого.
> 2) Он успешно компилируется по той же причине, что и с
> полем:
> Val - это свойство потомка TB
Да, никакой разницы между полем и свойством в данном контексте нет. Свойство написал по привычке, никакого скрытого смысла не подразумевалось. Демонстрация состояла в том, что мы обращаемся к свойству (полю), которого у объекта TA в принципе нет, а значит выражение имеет тип TB. Почему оно имеет такой тип - я предположил выше.
> Посмотрите справку в Дельфи по "Fields"
Fields are statically bound; that is, references to them are fixed at compile time. To see what this means, consider the following code:
Там кусок кода приведен для демонстрации механизмов работы Делфи, а не в качестве "делайте вот так". То, что делать вот так - не стоит (по крайней мере не имея весьма веских причин, которых не приводилось) для меня очевидно. Думаю, другие обитатели форума найдут более весомые аргументы, вступать в полемику по сему поводу считаю малополезным занятием.
И. Павел (2010-12-08 12:01:00) [25]Если подвести курсор мыши к Val в строке (B as TA).Val то подсказка сообщает: TB.Val
Чертовщина.
А в новых версиях Delphi это не поправили?
Nickolay (2010-12-08 11:47:00) [26]
> Servy © (08.12.10 11:30) [12]
1) Если компилятор может проверить на стадии компиляции, что B принадлежит типу TA, то он просто выкидывает as.
2) Данный код успешно компилируется, хотя в типе TA вообще нет никакого Val.
3) Да, ну и очевидно, что давать одинаковые имена полям предка и потомка - плохая идея
1) Для проверки принадлежности классу есть другая операция - is
2) Он успешно компилируется по той же причине, что и с полем:
Val - это свойство потомка TB
3) Посмотрите справку в Дельфи по "Fields". Там как раз приводится пример по переобъявлению полей, который завершается фразой:
"Both fields, however, exist in the TDescendant object; the inherited Value is hidden by the new one, and can be accessed through a typecast".
А вот "typecast", похоже, можно сделать только с помощью функционального преобразования.
Servy (2010-12-08 11:30:00) [27]> Nickolay
Весьма забавное наблюдение, я при некотором, пусть далеко не всеобъемлющем опыте программирования на Делфи на такой вариант использования as не натыкался. Спасибо, что поделились.
На самом деле, все более менее логично, хотя в справке явно не описано. Если компилятор может проверить на стадии компиляции, что B принадлежит типу TA, то он просто выкидывает as. Легко убедиться в этом, например так:
type
TA = class
end;
TB = class(TA)
private
FVal: Integer;
public
property Val: Integer read FVal write FVal;
end;
var
B: TB;
A: TA;
begin
B := TB.Create;
(B as TA).Val := 2;
Данный код успешно компилируется, хотя в типе TA вообще нет никакого Val. Если изменить тип переменной B на TObject, то компилироваться данный код перестанет. Так как все поля предка принадлежат и потомку, то никаких проблем в стандартных ситуациях не возникает.
А вот если вы используете приведение к неизвестному на этапе компиляции классу (в вашем примере Aref), то от as компилятору не избавиться и он выдает ожидаемый вами результат.
Да, ну и очевидно, что давать одинаковые имена полям предка и потомка - плохая идея, которая приводит к плохой читаемости. Посему, видимо, на эти грабли почти никто и не наступал. Если первая реинкарнация Val и вторая - логически одна и та же переменная, то можете использовать тип Variant, способный хранить как число, так и строку или другой тип данных. Если же это две различных переменных с различными значениями, дайте им разные имена, чтобы надпись A.Val не могла быть понята превратно (ни компилятором, ни человеком).
И. Павел (2010-12-08 11:20:00) [28]> Self - это переменная. Указатель, указывающий либо на данный
> экземпляр класса, либо на сам класс в случае классовых методов.
> Для обращения к методам предка используется директива inherited
Точно. Я спутался. inherited, конечно.
Nickolay (2010-12-08 11:20:00) [29]
> И. Павел © (08.12.10 10:48) [5]
>
> В паскале используйте self. для доступа к элементам предка.
>
Строго говоря self для доступа к текущему объекту класса. Это удобно для
списка динамических объектов. Операция же as специально для преобразования типа.
Ega23 (2010-12-08 11:19:00) [30]
> Поясните.
Self - это переменная. Указатель, указывающий либо на данный экземпляр класса, либо на сам класс в случае классовых методов.
Для обращения к методам предка используется директива inherited
И. Павел (2010-12-08 11:05:00) [31]> Это вредный совет. Неправильный.
А это - необоснованное возражение.
Поясните.
RWolf (2010-12-08 11:03:00) [32]
> [3] B - потомок TA.
да, моя ошибка.
в принципе, похоже на баг.
var c:ta;
(B as TA).Met; // вызывает TB.Met
c:=b as ta;
c.met; //вызывает TA.Met
Ega23 (2010-12-08 10:55:00) [33]
> В паскале используйте self. для доступа к элементам предка.
Это вредный совет. Неправильный.
И. Павел (2010-12-08 10:48:00) [34]> спец операция ::,
В паскале используйте self. для доступа к элементам предка.
И еще для переопределения в паскале есть спец. слово (посмотрите описание стандартных классов, и вы его увидите). Савсем недавно на форуме обсуждали: зачем его добавили. Вот видимо чтобы сишников на первых порах мучать.
Nickolay (2010-12-08 10:44:00) [35]
> Palladin © (08.12.10 09:51) [2]
Смысл имеют: во-первых, это определение операции as,
во-вторых, в C++ такие перекрытия полей тоже возможны и
для такого случая там тоже есть спец операция ::,
а в Delphi для типа класс постарались взять многие возможности
из C++.
Nickolay (2010-12-08 10:40:00) [36]
> RWolf © (08.12.10 09:34) [1]
> Открываем справку:
object as class
object must be an instance of the class denoted by class or one of its descendants, or be nil
Объект object должен быть экземпляром класса, обозначенного правым операндом class, или одним из его потомков, или константой nil.
У меня B - потомок TA.
Palladin (2010-12-08 09:51:00) [37](B as TA).Val := 18; // здесь операция as не работает - ошибка
(B as TA).Met; // здесь операция as не работает - вызов метода потомка
не имеют смысла
RWolf (2010-12-08 09:34:00) [38]Открываем справку:
>object as class
> … object must be an instance of the class denoted by class
> or one of its descendants, or be nil