1
votes

When you define a Class (or an Object or Record with methods), I was just wondering if there's any convention as to whether the data fields should appear before the method fields?

I mean like the difference between the two cases in the following trivial example. (edited to make the specific problem clearer)

type TWhatever = Object
      private
        a,b : Integer;
      public
        procedure SetWhatever(x,y :Integer);
        procedure ShowWhatever;
      end;

versus

type TWhatever = Object
      public
        procedure SetWhatever(x,y :Integer);
        procedure ShowWhatever
      private
        a,b : Integer;
     end;

I've always used the convention of listing the data fields first, but to be honest I didn't really think it made any difference. Recently however I came across some code that didn't work (wouldn't compile) unless the data fields were listed first.

I've documented the case here: Delphi 2006 wont allow const parameters of type record within record method?

The summary is that if you put the data fields after the methods, then the code wont compile if the object has any methods that try to pass the given object as "const" parameter. For example something like this won't compile:

procedure TWhatever.SomeMethod( const : w1: TWhatever);

Apparently it will compile on later versions of Delphi, but I've tested it on both Delphi7 and Delphi 2006 and it wont compile on either.

The problem only appears if you do all three things (data fields last, pass object as parameter in method, use the const keyword for this parameter). So you can solve the problem by either removing the const keyword from the parameter or by putting the data fields ahead of the methods.

Anyway, this issue has just made me wonder if there is a convention to be followed here?

EDITED I added this to give a specific code example

The following program fails to compile on both D7 and D2006:

program bugtest;
{$APPTYPE CONSOLE}
uses SysUtils;

type Tob = object
   public
    procedure setOb(const a,b: integer);
    procedure addToOb(const ob1: Tob);
    procedure printOb;
   private
    x,y : Integer;
   end;

procedure Tob.setOb(const a,b: integer);
begin
  x:=a; y:=b;
end;

procedure Tob.addToOb(const ob1: Tob);
begin
  x:=x+ob1.x; y:= y+ob1.y;
end;

procedure Tob.printOb;
begin
  writeln(x,' ',y);
end;

var r1,r2: Tob;
begin
  r1.setOb(2,3);
  r2.setOb(10,100);
  r1.addToOb(r2);
  r1.printOb;
  r2.printOb;
  readln;
end.

The following program compiles and runs perfectly on both D7 and D2006:

program bugtest;
{$APPTYPE CONSOLE}
uses SysUtils;

type Tob = object
   private
    x,y : Integer;
   public
    procedure setOb(const a,b: integer);
    procedure addToOb(const ob1: Tob);
    procedure printOb;
   end;

procedure Tob.setOb(const a,b: integer);
begin
  x:=a; y:=b;
end;

procedure Tob.addToOb(const ob1: Tob);
begin
  x:=x+ob1.x; y:= y+ob1.y;
end;

procedure Tob.printOb;
begin
  writeln(x,' ',y);
end;

var r1,r2: Tob;
begin
  r1.setOb(2,3);
  r2.setOb(10,100);
  r1.addToOb(r2);
  r1.printOb;
  r2.printOb;
  readln;
end.

Delphi 7 version is: Delphi Personal Version 7.0 build 4.453.

D2006 version is: Borland Delphi for MS Windows. Version 10.0.2288.42451, Update2.

Screenshot of compiler

2
How did you get, that it's not possible to define method which uses the class itself as a parameter ? That's what works...TLama
To be honest I haven't tried it for the "class" type, but it's definitely not a problem doing this with either the object type or the record (with methods) type. Note that records with methods is only in D2006 onward.Stuart
Using just defined record or object type itself as a certain parameter type of a method compiles without a problem in Delphi 2009.TLama
If I am not wrong the original Pascal required that var clause should precede all procedure&function declarations in every code block.kludg
There's probably a convention to put the fields before the methods. In other languages the convention would be reversed. You can do it however you please. In your case there's obviously a benefit from swapping it round from the way I wrote it. I wanted to put the operators first.David Heffernan

2 Answers

1
votes

I think it's fair to say that there is a widely used convention. Typically a class declaration would be arranged like this:

TMyClass = class
  // published declarations, managed by form designer
  Component1: TMyComponent;
  procedure MyEventHandler(Sender: TObject);
private
  FMyPrivateField: Integer;
  procedure MyPrivateMethod;
protected
  FMyProtectedField: Integer;
  procedure MyProtectedMethod;
public
  FMyPublicField: Integer;
  procedure MyPublicMethod;
published
  FMyPublishedField: Integer;
  procedure MyPublishedMethod;
end;

In other words the sections would be organised with the form designer's declarations first, and then in order of increasing visibility.

Interestingly other conventions are used in other languages. For example, take a look at the Google C++ style guidelines where the order of sections is the reverse of the above.

Personally I would prefer to see the public declarations first. When reading code that already exists I am typically reading with a top-down perspective. And often when writing code, I find that a top-down approach is beneficial.

Looking at my TComplex record, there is a reason why the declarations are ordered as they are. I have a number of such similar records and some of them are variant records. For example:

type
  TVector = record
  public
    class operator Negative(const V: TVector): TVector;
    class operator Equal(const V1, V2: TVector): Boolean;
    class operator NotEqual(const V1, V2: TVector): Boolean;
    class operator Add(const V1, V2: TVector): TVector;
    class operator Subtract(const V1, V2: TVector): TVector;
    class operator Multiply(const V: TVector; const D: Double): TVector;
    class operator Multiply(const D: Double; const V: TVector): TVector;
    class operator Divide(const V: TVector; const D: Double): TVector;
    function IsZero: Boolean;
    function IsNonZero: Boolean;
    function IsUnit: Boolean;
    function Max: Double;
    function Sum: Double;
    function Mag: Double;
    function SqrMag: Double;
    function MagNormalComponent: Double;
    procedure MakeAxisymmetric;
    class function SNaN: TVector; static;
    function GetHashCode: Integer; inline;
  public
    case Boolean of
    False:
      (X, Y, Z: Double);
    True:
      (a: array [1..3] of Double);
  end;

Now, the variant part of the record must appear last. It is a compilation error to do otherwise. And so there's simply no choice for this record. Having established the convention for TVector and related records, TComplex simply followed suit.

4
votes

As per the compiler, fields declarations goes before any method declaration in any class section. There may be any number of sections on a class, even with the same visibility.

For example:

type
  TMyObject = class
  private
    FTest: Integer;
    procedure A();
  private
    FOtherTest: Integer;
    procedure B();
  end;

compiles and you can argue if FOtherTest is after procedure A() or not.

As this is enforced by the compiler, there's no way to make it other way, so I'm not sure if call it a convention, but it have to be that way.

On the other hand,

type
  TMyObject = class
  private
    procedure A();
    FTest: Integer;
  end;

doesn't compile, with the error:

E2169 Field definition not allowed after methods or properties

It doesn't matter if you pass it as any type of parameter. In my experience, the error is emmited if you just declare any field after a method.

Edit

After the question was edited, the OP says a object (different than a class) doesn't compile in Delphi 7 or 2006 if a Field is declared in a different section after a section having methods, if the object passed as a const parameter to a routine.

I managed to run my very old D7 in a VM and this example compiles OK:

enter image description here

It compiles with a object or class declaration, and since records didn't support methods on that version, obviously a record declaration doesn't compile.

Edit2

It also compiles if the object is passed as a const parameter to a method (regardless if is a method of the same object or other)

enter image description here