为什么没有引发NullReferenceException?

san*_*oIT 5 delphi delphi-xe2

我已经向几位同事展示了这一点,没有人有解释.我碰巧遇到了这个问题,因为我认为我在代码中发现了一个错误,但是很惊讶地看到代码实际运行了.这是一个简化版本.这是用XE-2完成的.

到目前为止我与之交谈的每个人都期望应该抛出NullReferenceException.

 TUnexplainable = class(TObject)
  public
    function Returns19: Integer;
  end;

function TUnexplainable.Returns19: Integer;
begin
  Result := 19;
end;
Run Code Online (Sandbox Code Playgroud)

以下测试永远不会有效,但它会成功运行.为什么没有抛出NullReferenceException ????

procedure TTestCNCStep.ShouldNeverEverWorkV4;
var
  Impossible: TUnexplainable;
  Int1: Integer;
begin
  Impossible := nil;
  Int1 := Impossible.Returns19; // A Null Reference Exception should ocurr here!!! Instead the method Returns19 is actually invoked!!!!
  Check(Int1 = 19);
end;
Run Code Online (Sandbox Code Playgroud)

测试结果

Rem*_*eau 11

非静态类方法被编译为具有隐藏Self参数的独立函数.因此,从编译器的角度来看,您的代码基本上是在执行以下操作:

//function TUnexplainable.Returns19: Integer;
function TUnexplainable_Returns19(Self: TUnexplainable): Integer;
begin
  Result := 19;
end;

//procedure TTestCNCStep.ShouldNeverEverWorkV4;
procedure TTestCNCStep_ShouldNeverEverWorkV4(Self: TTestCNCStep);
var
  Impossible: TUnexplainable;
  Int1: Integer;
begin
  Impossible := nil;
  Int1 := TUnexplainable_Returns19(Impossible);
  Check(Int1 = 19);
end;
Run Code Online (Sandbox Code Playgroud)

如您所见,Returns19()没有引用Self任何内容,因此没有理由发生nil指针错误.

更改代码以执行某些操作Self,然后您将看到预期的错误:

type
  TUnexplainable = class(TObject)
  public
    Number: Integer;
    function ReturnsNumber: Integer;
  end;

function TUnexplainable.ReturnsNumber: Integer;
begin
  Result := Number;
end;

procedure TTestCNCStep.ShouldNeverEverWorkV4;
var
  Impossible: TUnexplainable;
  Int1: Integer;
begin
  Impossible := nil;
  Int1 := Impossible.ReturnsNumber; // An EAccessViolation exception will now occur here!!!
end;
Run Code Online (Sandbox Code Playgroud)

我已经向几位同事展示了这一点,没有人有解释.

我非常担心与一组同事(假设他们是程序员)一起工作,他们不了解类方法和Self参数实际工作的基础知识,以及如何通过nil指针调用类方法可以避免空指针错误.这就像面向对象编程101这样的东西.


500*_*ror 5

你的方法从不引用Self指针,所以它恰好工作.

与C#不同,Delphi不会自动验证Self(this在C#中)指针.