Подтвердить что ты не робот

Назначено vs <> nil

Есть ли разница между If Assigned(Foo) и If (Foo <> nil)? Если So, когда они должны использоваться каждый?

4b9b3361

Ответ 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!