TL; DR
Официальная документация штатов
Assigned(P)
соответствует тесту P <> nil
для переменной-указателя и @P <> nil
для процедурной переменной.
Следовательно, для непроцедурной переменной-указателя (такой как переменная типа PInteger
, PMyRec
, TBitmap
, TList<integer>
или TFormClass
) Assigned(P)
- это то же самое, что и P <> nil
.
Однако для процедурной переменной Assigned(P)
- это то же самое, что и @P <> nil
, тогда как P <> nil
попытается выполнить процедуру или функцию, на которые P
указывает (с пустым список параметров).
Объяснение
Выдержка из документации, приведенная выше, обобщает это довольно хорошо. Assigned(X)
возвращает True
тогда и только тогда, когда переменная X
, которая должна быть указателем под капотом (за некоторым исключением), имеет значение не nil
.
Assigned
может использоваться для переменных указателя старой школы:
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
также может использоваться для переменных объекта (и метакласса). Действительно, в Delphi переменная объекта (или метакласса) - это просто указатель изнутри:
L := TList<integer>.Create;
try
// Assigned(L) True
// L <> nil True
finally
FreeAndNil(L);
end;
// Assigned(L) False
// L <> nil False
(И, для полноты, пример с переменной метакласса:
var
FC: TFormClass;
begin
FC := TForm;
// Assigned(FC) True
// FC <> nil True
FC := nil;
// Assigned(FC) False
// FC <> nil False
)
Во всех этих примерах Assigned(X)
- это то же самое, что и X <> nil
.
Однако для процедурных типов все немного по-другому.
Сначала давайте согреемся:
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;
Обратите внимание, в частности, что SP
используется для фактического вызова процедуры, на которую он в данный момент указывает.
Теперь вы можете попробовать
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;
но это даже не скомпилируется. Компилятор говорит: «Недостаточно фактических параметров». Причина в том, что приведенный выше код попытается выполнить процедуру, на которую указывает SP
, и тогда действительно требуемый параметр AText
отсутствует. (Конечно, во время компиляции компилятор не знает, будет ли SP
указывать на совместимую процедуру или нет, но он знает сигнатуру такой допустимой процедуры.)
И даже если бы у процедурного типа был пустой список параметров, он не скомпилировался бы, поскольку процедура не возвращает значение (а тем более значение, которое можно сравнить с nil
).
Но будьте осторожны! Следующий код скомпилирует :
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;
Первый PF <> nil
будет сравнивать значение результата функции MyPtrFunc
с nil
; не сообщит вам, назначен ли указатель функции PF
или нет (это так!).
Второй PF <> nil
попытается вызвать nil
указатель на функцию; это ошибка (исключение нарушения прав доступа).
Чтобы проверить, назначена ли процедурная переменная, вы должны проверить @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;
Для процедурных переменных Assigned(X)
- это то же самое, что и @X <> nil
, как указано в документации.
Методы
Методы работают как обычные процедуры в отношении этой темы. Например, для переменной метода M
, Assigned(M)
эквивалентно @M <> nil
и равно True
, если указатель метода не равен nil
. (Я думаю, что под капотом @M
получается Code
член 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;
Что использовать?
Итак, вы должны использовать Assigned(X)
или X <> nil
для непроцедурных указателей? И следует ли вам использовать Assigned(X)
или @X <> nil
для процедурных указателей? Это дело вкуса.
Лично , я склонен использовать Assigned(X)
, когда я хочу проверить, присвоена ли переменная, и X = nil
(или @X = nil
), когда я хочу проверить, является ли переменная не назначено, просто потому что not Assigned(X)
менее компактен.
Соответствующее предупреждение
Конечно, Assigned(X)
и X <> nil
(или @X <> nil
) только проверяют, является ли указатель nil
или нет; если не - nil
, указатель все еще может указывать на мусор. Например, поскольку локальные неуправляемые переменные не инициализируются в Delphi, они вполне могут быть не nil
до того, как им присвоено значение, но в этом случае они указывают на мусор:
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).
Другой пример:
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!