12

The following Delphi program calls method upon nil reference and runs fine.

program Project1;

{$APPTYPE CONSOLE}

type
  TX = class
    function Str: string;
  end;

function TX.Str: string;
begin
  if Self = nil then begin
    Result := 'nil'
  end else begin
    Result := 'not nil'
  end;
end;

begin
  Writeln(TX(nil).Str);
  Readln;
end.

However, in a structurally similar C# program, System.NullReferenceException will be raised, which seems to be the right thing to do.

namespace ConsoleApplication1
{
    class TX
    {
        public string Str()
        {
            if (this == null) { return "null"; }
            return "not null";    
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            System.Console.WriteLine(((TX)null).Str());
            System.Console.ReadLine();
        }
    }
}

Because TObject.Free uses such style, it seems to be "supported" to call method on nil reference in Delphi. Is this true ? (Let's suppose that in the if Self = nil branch, no instance field will be accessed.)

SOUser
  • 3,802
  • 5
  • 33
  • 63
  • 3
    Related, http://stackoverflow.com/questions/1040860/why-would-you-check-for-assignedself-in-object-methods – H H Nov 18 '15 at 08:35
  • @HenkHolterman Thank you very much for the valuable information ! – SOUser Nov 18 '15 at 08:39

1 Answers1

19

It is reasonable to call a method on a nil reference, subject to the following rules:

  1. The method must not be virtual or dynamic. That is because virtual or dynamic methods are bound using the runtime type of the reference. And if the reference is nil then there is no runtime type. By way of contrast, non-virtual, non-dynamic methods are bound at compile time.
  2. You are allowed to read the value of Self, for instance to compare it against nil.
  3. In case Self is nil, then you must not refer to any instance variables.
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Thank you very much for your help ! Could you comment whether you get these rules from official documentations, especially the part about `not virtual (or dynamic)` ? I did not find any official documentation page (or DelphiBasics page) saying so. – SOUser Nov 18 '15 at 08:57
  • 1
    @XichenLi, the VMT table is initialized in the constructor. Without initialization, a virtual (dynamic) call does not work. – LU RD Nov 18 '15 at 09:01
  • 1
    @XichenLi, see [Internal Data Formats.Class Types](http://docwiki.embarcadero.com/RADStudio/en/Internal_Data_Formats#Class_Types). – LU RD Nov 18 '15 at 09:14
  • 1
    I'm not sure about documentation of this, but what I stated above is just what I know to be true. – David Heffernan Nov 18 '15 at 09:24
  • @LURD Thank you for your help very much ! I am still confused. Why calling regular method works ? It needs to check `vmtMethodTable` in the VMT table anyway. If VMT table is not initialized, how can it find the entry point ? – SOUser Nov 18 '15 at 09:26
  • @DavidHeffernan Does your statement `it does not work because there is no runtime type when the reference is nil` mean the same thing as LU RD's statement `it does not work because VMT table is not initialized when the reference is nil` ?.... O_O – SOUser Nov 18 '15 at 09:29
  • 1
    We are stating the same thing in different ways. I've expressed it in a higher level way, by pointing out that virtual and dynamic binding requires knowledge of the runtime type. That happens to be implemented by a VMT or DMT, but those are implementation details. On the other hand non virtual, non dynamic methods are bound at compile time. – David Heffernan Nov 18 '15 at 09:36
  • 2
    @XichenLi *Why calling regular method works ? It needs to check vmtMethodTable in the VMT table anyway.* No, that is not so. These methods are bound at compile time. – David Heffernan Nov 18 '15 at 09:37
  • @DavidHeffernan Thank you very much for your helpful comments ! – SOUser Nov 18 '15 at 09:55
  • 2
    @David - I think it worth saying that violation of any of these rules will not be caught by the compiler and doing so will result in a generic "access violation" exception rather than a more helpfully diagnostic error identifying the specific transgression of these rules (e.g. NulLReferenceException). An experienced developer should be able to deduce a NIL reference from the details of the access violation error message, but it is not spelled out in the same way that the specific exception in .NET does for you. – Deltics Nov 18 '15 at 18:38