Assigned vs <> nil
TL;DR
The official documentation states
Assigned(P)
corresponds to the testP <> nil
for a pointer variable, and@P <> nil
for a procedural variable.
Hence, for a non-procedural pointer variable (such as a variable of type PInteger
, PMyRec
, TBitmap
, TList<integer>
, or TFormClass
), Assigned(P)
is the same thing as P <> nil
.
However, for a procedural variable, Assigned(P)
is the same thing as @P <> nil
, while P <> nil
would try to execute the procedure or function that P
points to (with an empty list of parameters).
Explanation
The documentation excerpt above summarizes it pretty well. Assigned(X)
returns True
if and only if the X
variable, which has to be a pointer under the hood (with some exception), has a non-nil
value.
Assigned
can be used for "old-school" pointer variables:
var
i: Integer;
p: PInteger;
begin
i := 5;
p := @i;
{ Assigned(p) True }
{ p <> nil True }
p := nil;
{ Assigned(p) False }
{ p <> nil False }
Assigned
can also be used for object (and metaclass) variables. Indeed, in Delphi, an object (or metaclass) variable is simply a pointer under the hood:
L := TList<integer>.Create;
try
{ Assigned(L) True }
{ L <> nil True }
finally
FreeAndNil(L);
end;
{ Assigned(L) False }
{ L <> nil False }
(And, for completeness, an example with a metaclass variable:
var
FC: TFormClass;
begin
FC := TForm;
{ Assigned(FC) True }
{ FC <> nil True }
FC := nil;
{ Assigned(FC) False }
{ FC <> nil False }
)
In all these examples, Assigned(X)
is exactly the same thing as X <> nil
.
However, for procedural types, things are slightly different.
First, let's warm up:
type
TStringProc = procedure(const AText: string);
procedure MyStrProc(const AText: string);
begin
ShowMessage(AText);
end;
procedure TForm1.FormCreate(Sender: TObject);
var
SP: TStringProc;
begin
SP := MyStrProc;
SP('test');
end;
Notice in particular that SP
is used to actually invoke the procedure it currently points to.
Now, you might try
procedure TForm1.FormCreate(Sender: TObject);
var
SP: TStringProc;
begin
SP := MyStrProc;
ShowMessage(BoolToStr(Assigned(SP), True)); { True }
ShowMessage(BoolToStr(SP <> nil, True)); { will not compile }
SP := nil;
ShowMessage(BoolToStr(Assigned(SP), True)); { False }
ShowMessage(BoolToStr(SP <> nil, True)); { will not compile }
end;
but that will not even compile. The compiler says, "Not enough actual parameters". The reason is that the above code will try to execute the procedure that SP
points to, and then indeed the required AText
parameter is missing. (Of course, at compile time, the compiler doesn't know if SP
will point to a compatible procedure or not, but it does know the signature of such a valid procedure.)
And even if the procedural type had an empty parameter list, it wouldn't compile, since a procedure doesn't return a value (much less a value that can be compared against nil
).
But beware! The following code will compile:
type
TGetPtrFunc = function: pointer;
function MyPtrFunc: pointer;
begin
Result := nil;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
PF: TGetPtrFunc;
begin
PF := MyPtrFunc;
ShowMessage(BoolToStr(Assigned(PF), True)); { True }
ShowMessage(BoolToStr(PF <> nil, True)); { False (!) }
PF := nil;
ShowMessage(BoolToStr(Assigned(PF), True)); { False }
ShowMessage(BoolToStr(PF <> nil, True)); { will cause access violation at runtime }
end;
The first PF <> nil
will compare the MyPtrFunc
function result value against nil
; it will not tell you whether the PF
function pointer is assigned or not (it is!).
The second PF <> nil
will try to invoke a nil
function pointer; that's a bug (access violation exception).
To test if a procedural variable is assigned, you have to test @PF <> nil
:
procedure TForm1.FormCreate(Sender: TObject);
var
SP: TStringProc;
begin
SP := MyStrProc;
ShowMessage(BoolToStr(Assigned(SP), True)); { True }
ShowMessage(BoolToStr(@SP <> nil, True)); { True }
SP := nil;
ShowMessage(BoolToStr(Assigned(SP), True)); { False }
ShowMessage(BoolToStr(@SP <> nil, True)); { False }
end;
procedure TForm1.FormCreate(Sender: TObject);
var
PF: TGetPtrFunc;
begin
PF := MyPtrFunc;
ShowMessage(BoolToStr(Assigned(PF), True)); { True }
ShowMessage(BoolToStr(@PF <> nil, True)); { True }
PF := nil;
ShowMessage(BoolToStr(Assigned(PF), True)); { False }
ShowMessage(BoolToStr(@PF <> nil, True)); { False }
end;
For procedural variables, Assigned(X)
is the same thing as @X <> nil
, as stated by the documentation.
Methods
Methods work as regular procedures as far as this topic is concerned. For instance, for a method variable M
, Assigned(M)
is equivalent to @M <> nil
and is True
iff the method pointer is not nil
. (Under the hood, I believe @M
yields the Code
member of TMethod
.)
procedure TForm1.FormCreate(Sender: TObject);
var
M: TNotifyEvent;
begin
M := Self.FormClick;
ShowMessage(BoolToStr(Assigned(M), True)); { True }
ShowMessage(BoolToStr(@M <> nil, True)); { True }
M := nil;
ShowMessage(BoolToStr(Assigned(M), True)); { False }
ShowMessage(BoolToStr(@M <> nil, True)); { False }
end;
What to use?
So, should you use Assigned(X)
or X <> nil
for non-procedural pointers? And should you use Assigned(X)
or @X <> nil
for procedural pointers? It is entirely a matter of taste.
Personally, I tend to use Assigned(X)
when I want to test if the variable is assigned, and X = nil
(or @X = nil
) when I want to test if the variable is not assigned, simply because not Assigned(X)
is less compact.
A related warning
Of course, both Assigned(X)
and X <> nil
(or @X <> nil
) only test if the pointer is nil
or not; if non-nil
, the pointer might still point to garbage. For instance, since local non-managed variables are not initialized in Delphi, they might well be non-nil
before they are assigned a value, but in that case they point to garbage:
procedure TForm1.FormCreate(Sender: TObject);
var
L: TList<integer>; { local non-managed variable: not initialized }
begin
Assigned(L) { True or False (chance). If True, it points to garbage data. }
{ Bad things will happen if you try to use L as a list here }
{ (especially if L is not nil). }
Another example:
L := TList<integer>.Create;
try
{ Do things with L }
finally
L.Free;
end;
Assigned(L); { True, but L points to garbage -- don't use it as a list! }