Type Declarations
Go Up to Delphi’s Object Pascal Style Guide
Types are declared in a type section (there are no strict rules in terms of how many type sections there can be in an interface or implementation section of a unit). Type identifiers are listed in one or more lines following the line with the type keyword and indented 2 spaces.
As a general rule, all type name identifiers are prefixed with a capital T, as in TMyType.
There are the following exceptions to this naming rule:
- Exception classes (derived from the class Exception) start with a capital E letter instead of capital T, as in EMyException
- Interface types are prefixed with the capital letter I instead of capital T, as in IMyInterface
- Pointer data types use the capital letter P
- Custom attribute classes (that is, classes descending from TCustomAttribute), which do not use the initial T and should end with the word Attribute (see the specific section later on)
- Data structures mapped to platform API (like WinAPI record types used as parameters) should retain their original name in the platform API
Contents
Basic Types Declarations
Data types are declared in specific blocks, which can be repeated in a source code file. The type keyword should appear on a specific line, flush to the left, with declarations indented in the following lines. The exception is nested types, which are indented within the type they are part of.
As already indicated, data type names should follow the Pascal Casing rules and start with a letter T (with the specific exceptions discussed). After the type name and on the same line you should have the equal sign with spaces on both sides, followed by the actual type declaration.
These are examples of an enumerated type, a set type, a type alias, and a subrange type:
type
TFontStyle = (fsBold, fsItalic, fsUnderline, fsStrikeOut);
TFontStyles = set of TFontStyle;
TFontName = type string;
TFontCharset = 0..255;
Other specific examples of more complex type declarations are listed in the following sections.
Special Rules for Enumerated Types
There is one exception for the naming rule (use Pascal casing) for the constant values of an old-style enumeration (before scoped enumerations were introduced). For the values, it was recommended to use value with the enumeration type name as a prefix - sort of an Hungarian exception. For example, FontStyle values would start with fs and ButtonKind values with bk. The reason lies in the fact that these constant values are global and a prefix helps reduce the risk of a name clash. Example:
type
TSeekOrigin = (soBeginning, soCurrent, soEnd);
TBitBtnKind = (bkCustom, bkOK, bkCancel, bkHelp,
bkYes, bkNo, bkClose, bkAbort, bkRetry, bkIgnore, bkAll);
For scoped enumerations, given they are used with the type as a prefix, use regular Pascal Casing notation with no prefix, for example:
type
TOSPlatform = (Windows, OSX, iOS, Android, Linux);
TSide = (Top, Left, Bottom, Right);
In general, we recommend using scoped enumerations over the traditional syntax. However, when working on existing libraries that use traditional enumerations (like VCL) it is a good practice to follow the library standard approach.
Array declarations
There should not be a space before the opening bracket "[" or within the square brackets:
type
TMyArray = array[0..100] of Char;
Similarly, a space is not present before the open square bracket when using an array variable.
Array1[20] := 22;
Function pointer types
Function pointer type declarations follow the general rules for procedure and functions declarations. Not having a name, the keyword is followed by the open parentheses with the parameters list (if needed) without a space.
Example:
type
TListSortCompare = function(Item1, Item2: Pointer): Integer;
Class (and Record) Declarations
Following the general rules, a class declaration has an identifier prefaced by a capital T and using Pascal Casing, followed by a space, an equals sign, and the lowercase word class.
Rules for records (which can have methods and a subset of the access specifiers) are similar and will be highlighted if relevant.
If you want to specify the ancestor for a class, add a parenthesis (with no space), the name of the ancestor class, and the closing parenthesis:
type
TMyClass = class(TObject)
Access specifier (or scoping) directives should use the same indentation of the class name they belong to), and declared in the order shown in this example:
type
TMyClass = class(TObject)
strict private
private
strict protected
protected
public
published
end;
There are six access levels for class members in Object Pascal: published, public, protected, strict protected, private, and strict private -- in order of decreasing accessibility.
Class Body Organization
The body of a class declaration should be organized in the following order, for each of the access specifier blocks:
- Field declarations
- Method declarations
- Property declarations
In the original style guide, there was a requirement that “fields, properties, and methods in your class should be arranged alphabetically by name” in the respective section. This is often the case in the RTL and VCL libraries and it remains the recommended approach, but it is considered acceptable to keep related fields and methods together. For fields, the position can also affect the memory layout. So we are dropping the alphabetic order from the requirement, but if an existing class has alphabetically sorted members it is recommended to add new members respecting that arrangement.
Field and Property Naming
Fields (member variables) use Pascal Casing and begin each type declaration with a capital F. Fields are generally not accessible to external classes. For records, fields might be public, and in this case, the initial F is omitted.
By contrast, properties use Pascal Casing with no prefix. Name methods for accessing properties (if needed) with an initial Get or Set word followed by the property name (with no underscore or other special elements). We also recommend to avoid writing a setter and getter method when this has no additional code than what’s needed to write or read a local field.
An example of fields and properties:
type
TMyClass = class(TObject)
private
FMyData: Integer;
procedure SetData(Value: Integer);
published
property MyData: Integer read FData write SetData;
end;
Method Naming
Method names should use the regular rules for naming and follow the rules for procedures and functions listed earlier. Method names should be imperative verbs or verb phrases.
Examples:
// GOOD method names:
ShowStatus, DrawCircle, AddLayoutComponent
// BAD method names:
MouseButton // noun phrase; doesn't describe function
drawCircle // starts with lower-case letter
Add_layout_component // uses underscores
ServerRunning // verb phrase, but not imperative (use IsServerRunning or StartServer)
As a general recommendation, a method to test some boolean property or condition of the class may start with Is and use the property of value name (IsVisible), start with Has for a generic condition (HasParent), or use Can for a feature (CanPaint). This is a general recommendation that needs to be adapted to the specific case.
If possible, a method declaration should appear on one line. A broken line is aligned two spaces in from the left, like for procedures and functions.
Event naming
Event handlers' names start with the word On and are directly mapped (with no setter and getter method) to a field having the same name (for example, OnEvent property, FOnEvent field). Calls that trigger the event execution should be wrapped in a method starting with Do and followed by the event name (as in DoEvent).
Constructor declarations and C++ compatibility
Place your constructors and destructors at the head of the list of methods in the public section (or the section they are part of).
If there is more than one constructor, and if you choose to give them all the same name, then sort them lexically by formal parameter list, with constructors having more parameters always coming after those with fewer parameters. This implies that a constructor with no arguments (if it exists) is always the first one.
The following constructors cannot be distinguished as different by C++ because both contain a single Integer parameter, and will be an error when HPP generation is turned on:
constructor Create(AValue : Integer);
constructor CreateWithWidth(AWidth : Integer);
Instead, use different types (preferred) or add a dummy parameter to distinguish the parameter list:
constructor Create(AValue : Integer);
constructor CreateWithWidth(AWidth : Integer; Dummy : Boolean);
Event handler types
Event handler types follow the same rules of function pointer type declarations (which in turn borrow from procedure and function declarations). As a rule, there is no space between the procedure and function keyword and the open parentheses. Examples:
type
TNotifyEvent = procedure(Sender: TObject) of object;
TGetStrProc = procedure(const S: string) of object;
Type helpers
Record and class helpers follow the same rules of records and classes. The name of the identifier should start with the letter T as any type, refer to the type it helps, and end with the word Helper. Example:
type
TUInt32Helper = record helper for UInt32 ...
TSideHelper = record helper for TSide ...
Interfaces
Interfaces are declared very similarly to classes. Differently from other types, interfaces don’t use the letter T as prefix, but the letter I instead.
Following general rules, an interface declaration should be indented two spaces. The body of the interface is indented by the standard indentation of two further spaces. The closing end keyword should also be indented two spaces, the same indentation as the interface type name. There should be a semi-colon following the closing end keyword.
All interface methods are inherently public and abstract; do not explicitly include these keywords in the declaration of an interface method.
Except as otherwise noted, interface declarations follow the same style guidelines as classes.
The body of an interface declaration should be organized in the following order:
- Interface method declarations
- Interface property declarations
The declaration styles of interface properties and methods are identical to the styles for class properties and methods. The GUID part of the interface declaration is indented as the body of the interface:
type
IStreamPersist = interface
['{B8CD12A3-267A-11D4-83DA-00C04F60B2DD}']
procedure LoadFromStream(Stream: TStream);
procedure SaveToStream(Stream: TStream);
end;
As for classes implementing multiple interfaces, in the case of a long list use the regular rules for line continuation, going to a new line, and indenting two additional spaces:
type
TMyObject = class(TInterfacedObject, ISerializable, IObservable,
IChangeListener, IChangeable)
private
...
Generic Types Declarations
Generic data types and generic methods within non-generic types are written following the same general rules and records, classes, interfaces, or methods. The angle brackets containing the generic types are not preceded or followed by extra spaces (on either side). If multiple generic types appear in a declaration, they are separated by a comma and a space.
The placeholder for a generic data type is generally indicated by the letter stand-alone letter T. When multiple generic type placeholders are present, it is recommended to give the generic type placeholders a more descriptive name, like the type placeholders TKey and TValue in TDictionay<T>, rather than using the following letters of the alphabet: T, U, V.
Examples:
type
TDictionary<TKey, TValue> = class
TList<T> = class(TEnumerable<T>)
Tuple<T,U>
Anonymous Method Types
Anonymous method type declarations follow the same rules of function and procedure declarations and of function pointers declarations but add some specific rules mostly related to the call site.
In the type declaration, as for any function pointer or reference type, there is no space between the procedure or function keyword and the open parenthesis indicating the parameters and their types, as in:
type
TListSortCompareFunc = reference to function(Item1, Item2: Pointer): Integer;
TInterceptBeforeNotify = reference to procedure(Instance: TObject;
Method: TRttiMethod; const Args: TArray<TValue>; out DoInvoke: Boolean;
out Result: TValue);
When you assign or pass a value to an anonymous method, you can write the function or procedure code directly, in place. In this case, the function or procedure code should be written following the standard rules and respecting the indentation level of the call site. Begin and end keywords need to be on separate lines; the end statement can be followed immediately by a comma, when additional parameters follow, by the closing parenthesis, or by a semicolon in case of an assignment of the anonymous method.
Examples of correct and incorrect anonymous methods invocations (from the actual Delphi libraries code, another rule not applied consistently):
// correct
ForEachOutputParameter(ProxyMethod,
procedure(Param: TDSProxyParameter)
begin
if Param.ParameterDirection = TDBXParameterDirections.ReturnParameter then
R := Param;
end);
// incorrect, as begin is on the same line as procedure
TThread.Queue(nil, procedure begin
FRootNode.Delete;
FRootNode := nil;
end);
// incorrect, writing an anonymous method fully inlined is an error
TThread.Synchronize(nil, procedure begin FCallback(FQueueName, Success, FForceExpand); end);
Attributes
Custom attribute class names do not use the initial T for types and end with the word Attribute. This final word can be omitted when referring to the attribute type between square brackets.
Custom attributes appear in code before the symbol they refer to within square brackets and with no extra spaces inside the square brackets. There are two scenarios (not followed consistently in the Delphi libraries code):
- For types and methods, they should be placed on a separate line preceding the symbol they refer to
- For fields and parameters, they should be inline and separated with a space from the symbol they refer to (the exception being a very long attribute, which can be placed on its own line before the symbol).
Parameters of custom attributes follow the general rules of functions parameters.
Examples:
type
WeakAttribute = class(TCustomAttribute)
UnsafeAttribute = class(TCustomAttribute)
AlignAttribute = class(TCustomAttribute)
TAggregatedObject = class(TObject)
private
[Unsafe] FController: IInterface;
[HPPGEN(HPPGenAttribute.mkFriend, 'DELPHICLASS TList__1<T>')]
TListHelper = record