TObjectHelper for easier debugging a cast mismatch and a typed FreeAndNil
Posted by jpluimers on 2018/10/10
The below came in really useful in an old project I took over that was full of bugs having to do with improper casts and FreeAndNil usage.
EDIT 20181010: WordPress.com keeps mangling angle-brackets in pre
and code
sections, so I added the code to a gist; see link below.
First the examples.
procedure TMyServer.UnbindFromIdTcpServerStatusContext(const aContext: TIdContext); var lClientSession: TClientSession; begin lClientSession := TObjectHelper.Cast<TClientSession>(aContext.Data); ... end; type TBaseDataInterface = class(TObject) strict private FDatabase: TIBDatabase; FTransaction: TIBTransaction; ... end; destructor TBaseDataInterface.Destroy(); begin TObjectHelper.FreeAndNil(FDatabase); TObjectHelper.FreeAndNil(FTransaction); ... inherited Destroy(); end;
And the implementation.
unit ObjectHelperUnit; interface type TObjectHelper = record class function Cast<T: class>(const aValue: TObject): T; static; class procedure FreeAndNil<T: class>(var Value: T); static; end; implementation uses System.SysConst, System.SysUtils; class function TObjectHelper.Cast<T>(const aValue: TObject): T; var lException: Exception; begin if Assigned(aValue) then begin if aValue is T then Result := T(aValue) else begin lException := EInvalidCast.CreateFmt('%s; actual type %s but expected %s.', [SInvalidCast, aValue.QualifiedClassName, T.QualifiedClassName]); raise lException; end; end else Result := nil; end; class procedure TObjectHelper.FreeAndNil<T>(var Value: T); begin System.SysUtils.FreeAndNil(Value); end; end.
–jeroen
Gist:
unit ObjectHelperUnit; | |
interface | |
type | |
TObjectHelper = record | |
class function Cast<T: class>(const aValue: TObject): T; static; | |
class procedure FreeAndNil<T: class>(var Value: T); static; | |
end; | |
implementation | |
uses | |
System.SysConst, | |
System.SysUtils; | |
class function TObjectHelper.Cast<T>(const aValue: TObject): T; | |
var | |
lException: Exception; | |
begin | |
if Assigned(aValue) then | |
begin | |
if aValue is T then | |
Result := T(aValue) | |
else | |
begin | |
lException := EInvalidCast.CreateFmt('%s; actual type %s but expected %s.', | |
[SInvalidCast, aValue.QualifiedClassName, T.QualifiedClassName]); | |
raise lException at ReturnAddress; | |
end; | |
end | |
else | |
Result := nil; | |
end; | |
class procedure TObjectHelper.FreeAndNil<T>(var Value: T); | |
begin | |
System.SysUtils.FreeAndNil(Value); | |
end; | |
end. |
procedure TMyServer.UnbindFromIdTcpServerStatusContext(const aContext: TIdContext); | |
var | |
lClientSession: TClientSession; | |
begin | |
lClientSession := TObjectHelper.Cast<TClientSession>(aContext.Data); | |
… | |
end; | |
type | |
TBaseDataInterface = class(TObject) | |
strict private | |
FDatabase: TIBDatabase; | |
FTransaction: TIBTransaction; | |
… | |
end; | |
destructor TBaseDataInterface.Destroy(); | |
begin | |
TObjectHelper.FreeAndNil(FDatabase); | |
TObjectHelper.FreeAndNil(FTransaction); | |
… | |
inherited Destroy(); | |
end; |
HeartWare said
I use this:
TYPE
TObjectHelper = CLASS HELPER FOR TObject
FUNCTION CastAs<T:CLASS> : T;
FUNCTION IsClass(T : TClass) : BOOLEAN;
END;
FUNCTION TObjectHelper.CastAs<T> : T;
BEGIN
IF IsClass(T) THEN Result:=T(Self) ELSE Result:=NIL
END;
FUNCTION TObjectHelper.IsClass(T : TClass) : BOOLEAN;
BEGIN
IF NOT Assigned(Self) THEN
Result:=FALSE
ELSE IF NativeInt(Self)<$10000 THEN
Result:=FALSE
ELSE TRY
Result:=Self IS T
EXCEPT
Result:=FALSE
END
END;
as in
VAR O : TObject;
VAR C : TControl;
C:=O.CastAs<TControl>;
C is then either NIL or a properly cast TControl. A bit like C#’s “as” operator.
HeartWare said
How do you format the source code to keep indentation etc.?
jpluimers said
It is WordPress. It fails in mysterious ways. Sorry. Make a gist, then include the link to the gist.
jpluimers said
You are right that the C# casts
as
and()
are different than in Delphi: in C#, theas
returnsnull
when it fails; the()
throws an exception. In Delphi, theas
throws an exception, and()
breaks in mysterious ways when the cast does not match.I wanted to stay close to the Delphi way, but have a more clear error message.
rvelthuis said
C#’s
as
does the same as C++’sdynamic_cast
.jpluimers said
Wow, it’s been so long ago I did C++, that I totally forgot about that. Thanks for the reminder!
Jacek said
This will not compile, no generic declaration of type T.
Or I do not know something :-)
jpluimers said
It is WordPress that irregularly removes angle brackets from
code
andpre
sections.uligerhardt said
Hi! I left a similar comment earlier on which seems to be gone. Did you delete it? Or maybe there is a bug in the blog software?
jpluimers said
I browsed through the last day of comments, but could not find anything from you. It must the WordPress.com that fails irregularly.
uligerhardt said
I miss more than one comment on WordPress sites that I’m sure I’ve posted. :-/
jpluimers said
What I usually do is verify in a second tab if the comment actually got posted.
WordPress is full of quirks and PHP cruft, but it is far more reliable than a few years ago.
uligerhardt said
Thanks for the tip!
jpluimers said
No problem. I have felt the same pain (:
Gloegg said
I think the source is missing a either in the class or on the methods.
For the Cast method it should even be
jpluimers said
It is WordPress. It often messes with angle brackets in
code
andpre
blocks. Fixed by now. Hopefully it stays fixed when I save it.