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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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
asand()are different than in Delphi: in C#, theasreturnsnullwhen it fails; the()throws an exception. In Delphi, theasthrows 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
asdoes 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
codeandpresections.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
codeandpreblocks. Fixed by now. Hopefully it stays fixed when I save it.