已分配 vs <> nil

33
这两个语句 If Assigned(Foo)If (Foo <> nil) 有什么区别吗?如果有,它们应该在什么情况下使用?

7
指派()更好。 - David Heffernan
1
@DavidHeffernan:但是与if foo<>nil相比,if not Assigned(foo)有点臃肿。话虽如此,我更喜欢使用Assigned - Ian Boyd
2个回答

47

TL;DR

官方文档中介绍:

Assigned(P) 对于指针变量,相当于测试 P <> nil;对于过程变量,则相当于测试 @P <> nil

因此,对于非过程指针变量(例如类型为 PIntegerPMyRecTBitmapTList<integer>TFormClass 的变量),Assigned(P) 等同于 P <> nil

然而,对于过程变量,Assigned(P) 等同于 @P <> nil,而 P <> nil 则会尝试执行 P 指向的过程或函数(不带参数)。

Explanation

上述文档摘录已经很好地总结了此问题。 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是相同的。 方法 就本话题而言,方法与常规过程的工作方式相同。例如,对于一个方法变量MAssigned(M)等同于@M <> nil,并且当方法指针不为nil时为True。(在底层,我相信@M产生TMethodCode成员。)
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! }

我认为这个问题只是关于语言的问题。同样,我们有MyType(Myvar).Prop,它与(MyVar as MyType).Prop相同。 - az01
4
如果MyVar没有MyType的实例,那么会抛出异常。但是MyType(MyVar).Prop不会。 - Fabricio Araujo

-1

Assigned()可以处理任何对象作为参数,并且它总是你想要调用的函数。 如果你得到一个空值,你绝对想要将其测试为“未赋值”,而不是因为空值<>nil而引发异常。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接