The Delphi type-compatibility rule for descendant classes allows you to use a descendant class where an ancestor class is expected. As I mentioned earlier, the reverse is not possible. Now, suppose the TDog class has an Eat method, which is not present in the TAnimal class. If the variable MyAnimal refers to a dog, it should be possible to call the function. But if you try, and the variable is referring to another class, the result is an error. By making an explicit typecast, you could cause a nasty run-time error (or worse, a subtle memory overwrite problem), because the compiler cannot determine whether the type of the object is correct and the methods you are calling actually exist.
To solve the problem, you can use techniques based on run-time type information (RTTI, for short). Essentially, because each object "knows" its type and its parent class, you can ask for this information with the is operator (or in some peculiar cases using the InheritsFrom method of TObject). The parameters of the is operator are an object and a class type, and the return value is a Boolean:
if MyAnimal is TDog then ...
The is expression evaluates as True only if the MyAnimal object is currently referring to an object of class TDog or a type descendant from TDog. This means that if you test whether a TDog object is of type TAnimal, the test will succeed. In other words, this expression evaluates as True if you can safely assign the object (MyAnimal) to a variable of the data type (TDog).
Now that you know for sure that the animal is a dog, you can make a safe typecast (or type conversion). You can accomplish this direct cast by writing the following code:
var MyDog: TDog; begin if MyAnimal is TDog then begin MyDog := TDog (MyAnimal); Text := MyDog.Eat; end;
This same operation can be accomplished directly by the second RTTI operator, as, which converts the object only if the requested class is compatible with the actual one. The parameters of the as operator are an object and a class type, and the result is an object converted to the new class type. You can write the following snippet:
MyDog := MyAnimal as TDog; Text := MyDog.Eat;
If you only want to call the Eat function, you might also use an even shorter notation:
(MyAnimal as TDog).Eat;
The result of this expression is an object of the TDog class data type, so you can apply to it any method of that class. The difference between the traditional cast and the use of the as cast is that the second approach raises an exception if the object type is incompatible with the type you are trying to cast it to. The exception raised is EInvalidCast (exceptions are described at the end of this chapter).
To avoid this exception, use the is operator and, if it succeeds, make a plain typecast (in fact, there is no reason to use is and as in sequence, doing the type check twice):
if MyAnimal is TDog then TDog(MyAnimal).Eat;
Both RTTI operators are very useful in Delphi because you often want to write generic code that can be used with several components of the same type or even of different types. When a component is passed as a parameter to an event-response method, a generic data type is used (TObject); so, you often need to cast it back to the original component type:
procedure TForm1.Button1Click(Sender: TObject); begin if Sender is TButton then ... end;
This is a common technique in Delphi, and I'll use it in examples throughout the book. The two RTTI operators, is and as, are extremely powerful, and you might be tempted to consider them as standard programming constructs. Although they are indeed powerful, you should probably limit their use to special cases. When you need to solve a complex problem involving several classes, try using polymorphism first. Only in special cases, where polymorphism alone cannot be applied, should you try using the RTTI operators to complement it. Do not use RTTI instead of polymorphism. This is bad programming practice, and it results in slower programs. RTTI has a negative impact on performance, because it must walk the hierarchy of classes to see whether the typecast is correct. As you have seen, virtual method calls require just a memory lookup, which is much faster.
Run-time type information (RTTI) involves more than the is and as operators. You can access detailed class and type information at run time, particularly for published properties, events, and methods. You'll find more on this topic in Chapter 4.