May 5, 2011
A very technical Delphi tip, possibly the first of a series. How to perform an as cast only if the reference is not nil.
This is a very technical Delphi tip, possibly the first of a series... or maybe not. Over the 5 years I've had this blog, I rarely covered purely technical tips, but it might change (particularly if you tell me you like it).
As if not nil
The problems at hand is how to perform an "as" cast (a cast to a derived class) but only if the reference is not nil. in fact if you write,
obj as TButton
it will raise an exception in case the obj is not a TButton, but also in case obj is not assigned. Now if you write:
if obj is TButton then
you'll equally get the exception in case the obj is not assigned. so we need to change the code to:
if Assigned (obj) and (obj is TButton)
plus we have to do the actual cast. Question: can we write a single function to provide this behavior?
TCast.GetAs
Well, that's the point of the tip. In fact it turns out we can use generics and write a generic function that has the type as parameter. However, we cannot use a global function (they don't support generics types) but a class function (the only function of a specific class). Here is the class definition and the method code:
Now we can call this like:
btn := TCast.GetAs<TButton> (Sender);
Generics open up a lot of new coding techniques to Delphi, this is just the tip of the iceberg. Hope you find it useful...
posted by
marcocantu @ 10:10AM | 15 Comments
[0 Pending]
15 Comments
Delphi Tech Tip 01 TCast.GetAs
I think if you do
btn := TCast.GetAs<TButton> (Sender);
then btn can also be nil, and you must check ik btn is
not nil when you want to have the tag of btn
btn := TCast.GetAs<TButton> (Sender);
btn.tag:= 1; //--> error when btn = nil
Comment by Christophe on May 5, 10:45
Delphi Tech Tip 01 TCast.GetAs
"it will raise an exception in case the obj is not a
TButton, but also in case obj is not assigned." -
wrong. "as" can cast nil to any class.
Comment by Mihail on May 5, 10:48
Delphi Tech Tip 01 TCast.GetAs
Thank you very sharing yet another example on how to use
generics. I'm looking forward to using them!
Comment by Tom Van Acker on May 5, 10:55
Delphi Tech Tip 01 TCast.GetAs
In fact this exact problem can be solved without
generics since ages. And also without having to check
if the result is assigned:
function TryCast(AObject: TObject; AClass: TClass; out
AResult): Boolean;
begin
Result := AObject is AClass;
if Result then
TObject(AResult) := AObject as AClass
else
TObject(AResult) := nil;
end;
Anyway, I agree that generics are really nice and I
use them alot.
Comment by Stefan Glienke
[http://delphisorcery.blogspot.com/]
on May 5, 11:12
Why use generics?
Is it worth it?
IMHO the following function will do the same
function GetAs(aClass: TClass; aObject: TObject):
TObject;
begin
if (aObject<>nil) and aObject.InheritsFrom(aClass)
then
result := aObject else
result := nil;
end;
And if you set it as inline, your code will be much
faster than using generics...
Comment by A.Bouchez
[http://synopse.info]
on May 5, 11:20
Interesting
Like A.Bouchez points out - it may be overkill. The
majority of Delphi developers are still stuck in
D7-D2005 compilers. But I love little helper classes
like this :)
What would be cool is to create methods that, if
possible, are backwards compatible. Not all kinds of
functions can do this ofcourse, but some.
So if we combine your class with A.Bouchez version,
and perhaps relabel the method to "CastTo" - it would
be a very interesting little snippet.
I would also add interface casting. It wont really
make any difference but at least conceptually it could
make sense. Want to cast something? Use the TCast
class. Much easier for beginners to navigate.
Comment by Jon Lennart Aasenden
[http://delphimax.wordpress.com]
on May 5, 12:34
Delphi Tech Tip 01 TCast.GetAs
@A.Bouchez - that's not the same, since the result
type of the generic version is the class queried for.
In yours, the result type is just TObject.
@Jon - 'What would be cool is to create methods that,
if possible, are backwards compatible'. Why? The only
reason for backwards compatibility is if you're
actually using older versions. From the POV of new
code, 'backwards compatibility' is just deadweight,
causing bloat and sub-optimal design decisions.
Comment by Chris Rolliston
[http://delphihaven.wordpress.com/]
on May 5, 13:26
Delphi Tech Tip 01 TCast.GetAs
The left operand of an "as" cast is always allowed to be nil. The result
is simply nil. (If you read the "PUREPASCAL" version of System._AsCast,
you'll get the wrong idea about the operator's behavior because it
incorrectly uses the "is" operator. The documentation matches the
assembler implementation.)
The GetAs function given here has *less* utility than the built-in
operator because the compiler will catch and disallow invalid type-
casts at compile time, whereas this function can only detect them at
run time. For example:
val := button as TEdit;
val := TCast.GetAs<TEdit>(button);
The first line won't compile because TButton and TEdit do not have a
subclass relation. The second line compiles and evaluates to nil.
Comment by Rob Kennedy
[http://www.cs.wisc.edu/~rkennedy/]
on May 5, 14:12
Delphi Tech Tip 01 TCast.GetAs
@Chris Rolliston:
There are other Object Pascal compilers beside Delphi.
While the mac compiler is in the pipeline, there is
still no alternative for Linux. Hence being compatible
with older versions of OP has it's uses
Comment by Jon Lennart Aasenden
[http://delphimax.wordpress.com]
on May 5, 14:30
Delphi Tech Tip 01 TCast.GetAs
@Jon Lennart Aasenden:
"There are other Object Pascal compilers beside Delphi."
Let me see... there's FPC, which deliberates attempts
to mimic Delphi; there's GNU Pascal, which no one
uses, even if it's just about still alive; and there's
older versions of Delphi itself.
"While the mac compiler is in the pipeline, there is
still no alternative for Linux. Hence being compatible
with older versions of OP has it's uses"
It has its uses only so far as you actually want to
target those platforms right now. Even then, surely
the desired standard is not 'backwards compatibility'
as such, but 'what FPC implements of current Delphi'.
Comment by Chris Rolliston
[http://delphihaven.wordpress.com/]
on May 5, 19:05
Delphi Tech Tip 01 TCast.GetAs
Marco, thanks very much for offering your tech tip. I,
for one, hope you do continue this as a series. If
anything, you will provide a forum for debate and
increase community participation and interest. Great
first tip! thanks
Comment by Colin Johnsun on May 6, 03:50
Delphi Tech Tip 01 TCast.GetAs
@Mihail, @Rob Kennedy, I believe that what Marco wants
to achieve (sorry Marco, but I found your explanation
unclear or erroneous and had to read the code to
understand) is a kind of "as" that would return nil
instead of raising an exception when the types are not
compatible. The case of nil Objects is not the problem
and never was for "as" (as you both pointed out),
while "is" would return False on nil, wrongly leading
to believe that "as" would fail.
The "old fashion" (non generic) implementation would
be:
if Sender is TButton then
Result := TButton(Sender) // never nil
else
Result := nil; // whether Sender not assigned or
wrong type
or:
try
Result := Sender as TButton
except
on EInvalidCast do
Result := nil;
end;
Comment by François
[http://fgaillard.com]
on May 6, 07:38
Delphi Tech Tip 01 TCast.GetAs
I think it's overkill, and using :
if Assigned (obj) and (obj is TButton)
Would be suffice.
Great tip for showing the power of Generics though
Keep the series going
Comment by Shane on May 6, 11:23
Delphi Tech Tip 01 TCast.GetAs
I would rather raise an Exception than returning a null
pointer.
Comment by stanleyxu2005
[http://mmjd.com/]
on May 8, 15:18
Delphi Tech Tip 01 TCast.GetAs
Am I the only one who expects this to break in some
future compiler update? :-)
(Grinning, running, and ducking.)
Warren
Comment by Warren
[]
on May 12, 15:40
Post Your Comment
Click
here for posting
your feedback to this blog.
There are currently 0 pending (unapproved) messages.