Runtime type checks
Code holding a base type sometimes needs something only one derived type
has. is asks, while the program runs, what an object really is.
type Message extends Object
declare function Summary( ) as ustring
sender as ustring
end type
function Message.Summary( ) as ustring
return "from " & sender
end function
type TextMessage extends Message
text as ustring
end type
type Reply extends TextMessage
inReplyTo as int32
end type
type Picture extends Message
w as int32
h as int32
end type
sub Show( byref m as Message )
print m.Summary( );
'' is asks what the object really is, at run time. It is true for
'' the type named and for anything derived from it.
if m is TextMessage then
'' Safe now: the object really is a TextMessage.
dim as TextMessage ptr t = cast( TextMessage ptr, @m )
print ": "; t->text;
end if
if m is Reply then
print " (reply to #" & cast( Reply ptr, @m )->inReplyTo & ")";
end if
if m is Picture then print ": a picture";
print
end sub
dim as TextMessage hello
hello.sender = "Ada" : hello.text = "hello"
dim as Reply answer
answer.sender = "Grace" : answer.text = "hi!" : answer.inReplyTo = 1
dim as Picture photo
photo.sender = "Alan" : photo.w = 640 : photo.h = 480
Show( hello )
Show( answer )
Show( photo )
Output
from Ada: hello
from Grace: hi! (reply to #1)
from Alan: a picture
m is TextMessage is true when the object is a TextMessage or anything
derived from one, which is why the reply passes both the TextMessage and
the Reply tests. Once it is true, cast( TextMessage ptr, @m ) views the
same object as a TextMessage, and its text field can be read.
The cast itself checks nothing. Cast to the wrong type and the program reads
whatever memory follows the base part, so always guard a cast like this with
is.
is reads the answer from the hidden table pointer, so the type must extend
Object, and it asks only about types derived from the variable's own type.
Both are checked when the program compiles. An interface value is the
exception: v is Note and v is ISize ask about the object behind v,
whatever its interface (Several interfaces).
| Asking | Refused with |
|---|---|
p is More, where p's type does not extend Object | TKO0799: 'is' needs run-time type information, and 'plain' has none: it does not extend 'object' |
t is TextMessage, where t is already a TextMessage | TKO0798: 'is textmessage' needs a type derived from 'textmessage', and 'textmessage' is not one |
A chain of if m is TextMessage ... elseif m is Picture ... is a select case on the type, and it has to be edited every time a new kind of message
appears. When the question is "what should this object do", the answer is a
virtual method, which each new type supplies for itself. Keep
is for the few places that truly need a derived type's own data.
Activities
- Add a
Voicemessage with asecondsfield and makeShowprint its length. - Replace the
istests with a virtualDetail( )method that each message type overrides. Which version is easier to extend?