Есть ли разница между If Assigned(Foo)
и If (Foo <> nil)
? Если So, когда они должны использоваться каждый?
Назначено vs <> nil
Ответ 1
TL; DR
Официальная документация гласит
Assigned(P)
соответствует тестуP <> nil
для переменной-указателя и@P <> nil
для процедурной переменной.
Следовательно, для переменной процедурного указателя non- (такой как переменная типа PInteger
, PMyRec
, TBitmap
, TList<integer>
или TFormClass
), Assigned(P)
- это то же самое, что и P <> nil
.
Однако для процедурной переменной Assigned(P)
- это то же самое, что и @P <> nil
, а P <> nil
будет пытаться выполнить процедуру или функцию, на которые указывает P
(с пустым списком параметров).
объяснение
Выдержка из документации, приведенная выше, обобщает это довольно хорошо. Assigned(X)
возвращает True
тогда и только тогда, когда переменная X
, которая должна быть указателем под капотом (за некоторым исключением), имеет значение non- 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
результата функции 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
для процедурных указателей non-? И следует ли вам использовать Assigned(X)
или @X <> nil
для процедурных указателей? Это дело вкуса.
Лично я склонен использовать Assigned(X)
когда я хочу проверить, назначена ли переменная, и X = nil
(или @X = nil
), когда я хочу проверить, не присвоена ли переменная, просто потому, что not Assigned(X)
менее компактен.
Связанное предупреждение
Конечно, Assigned(X)
и X <> nil
(или @X <> nil
) только проверяют, является ли указатель nil
или нет; если non- nil
, указатель все еще может указывать на мусор. Например, поскольку локальные управляемые переменные non- не инициализируются в Delphi, они могут быть равны non- 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!