Methods (Delphi)

From RAD Studio
Jump to: navigation, search

Go Up to Classes and Objects Index

A method is a procedure or function associated with a class. A call to a method specifies the object (or, if it is a class method, the class) that the method should operate on. For example, SomeObject.Free calls the Free method in SomeObject.

This topic covers the following material:

  • Method declarations and implementation
  • Method binding
  • Overloading methods
  • Constructors and destructors
  • Message methods

About Methods

Within a class declaration, methods appear as procedure and function headings, which work like forward declarations. Somewhere after the class declaration, but within the same module, each method must be implemented by a defining declaration. For example, suppose the declaration of TMyClass includes a method called DoSomething:

 type
    TMyClass = class(TObject)
       ...
       procedure DoSomething;
       ...
    end;

A defining declaration for DoSomething must occur later in the module:

 procedure TMyClass.DoSomething;
 begin
      ...
 end;

While a class can be declared in either the interface or the implementation section of a unit, defining declarations for a class methods must be in the implementation section.

In the heading of a defining declaration, the method name is always qualified with the name of the class to which it belongs. The heading can repeat the parameter list from the class declaration; if it does, the order, type, and names of the parameters must match exactly, and if the method is a function, the return value must match as well.

Method declarations can include special directives that are not used with other functions or procedures. Directives should appear in the class declaration only, not in the defining declaration, and should always be listed in the following order:

reintroduce; overload; binding; calling convention; abstract; warning

Where:

All Delphi directives are listed in Directives.

Inherited

The reserved word inherited plays a special role in implementing polymorphic behavior. It can occur in method definitions, with or without an identifier after it.

If inherited is followed by the name of a member, it represents a normal method call or reference to a property or field, except that the search for the referenced member begins with the immediate ancestor of the enclosing method's class. For example, when:

 inherited Create(...);

occurs in the definition of a method, it calls the inherited Create.

When inherited has no identifier after it, it refers to the inherited method with the same name as the enclosing method or, if the enclosing method is a message handler, to the inherited message handler for the same message. In this case, inherited takes no explicit parameters, but passes to the inherited method the same parameters with which the enclosing method was called. For example:

 inherited;

occurs frequently in the implementation of constructors. It calls the inherited constructor with the same parameters that were passed to the descendant.

Self

Within the implementation of a method, the identifier Self references the object in which the method is called. For example, here is the implementation of TCollection Add method in the Classes unit:

 function TCollection.Add: TCollectionItem;
 begin
     Result := FItemClass.Create(Self);
 end;

The Add method calls the Create method in the class referenced by the FItemClass field, which is always a TCollectionItem descendant. TCollectionItem.Create takes a single parameter of type TCollection, so Add passes it the TCollection instance object where Add is called. This is illustrated in the following code:

 var MyCollection: TCollection;
     ...
     MyCollection.Add   // MyCollection is passed to the 
                        // TCollectionItem.Create method

Self is useful for a variety of reasons. For example, a member identifier declared in a class type might be redeclared in the block of one of the class' methods. In this case, you can access the original member identifier as Self.Identifier.

For information about Self in class methods, see "Class Operators" in Class References.

Method Binding

Method bindings can be static (the default), virtual, or dynamic. Virtual and dynamic methods can be overridden, and they can be abstract. These designations come into play when a variable of one class type holds a value of a descendent class type. They determine which implementation is activated when a method is called.

Static Methods

Methods are by default static. When a static method is called, the declared (compile-time) type of the class or object variable used in the method call determines which implementation to activate. In the following example, the Draw methods are static:

 type
     TFigure = class
       procedure Draw;
     end;
 
     TRectangle = class(TFigure)
       procedure Draw;
     end;

Given these declarations, the following code illustrates the effect of calling a static method. In the second call to Figure.Draw, the Figure variable references an object of class TRectangle, but the call invokes the implementation of Draw in TFigure, because the declared type of the Figure variable is TFigure:

 var
     Figure: TFigure;
     Rectangle: TRectangle;
 
     begin
             Figure := TFigure.Create;
             Figure.Draw;              // calls TFigure.Draw
             Figure.Destroy;
             Figure := TRectangle.Create;
             Figure.Draw;              // calls TFigure.Draw
 
             TRectangle(Figure).Draw;  // calls TRectangle.Draw
 
             Figure.Destroy;
             Rectangle := TRectangle.Create;
             Rectangle.Draw;          // calls TRectangle.Draw
             Rectangle.Destroy;
     end;

Virtual and Dynamic Methods

To make a method virtual or dynamic, include the virtual or dynamic directive in its declaration. Virtual and dynamic methods, unlike static methods, can be overridden in descendent classes. When an overridden method is called, the actual (run-time) type of the class or object used in the method call--not the declared type of the variable--determines which implementation to activate.

To override a method, redeclare it with the override directive. An override declaration must match the ancestor declaration in the order and type of its parameters and in its result type (if any).

In the following example, the Draw method declared in TFigure is overridden in two descendent classes:

 type
     TFigure = class
       procedure Draw; virtual;
     end;
 
     TRectangle = class(TFigure)
       procedure Draw; override;
     end;
 
     TEllipse = class(TFigure)
       procedure Draw; override;
     end;

Given these declarations, the following code illustrates the effect of calling a virtual method through a variable whose actual type varies at run time:

 var
    Figure: TFigure;
 
    begin
      Figure := TRectangle.Create;
      Figure.Draw;      // calls TRectangle.Draw
      Figure.Destroy;
      Figure := TEllipse.Create;
      Figure.Draw;      // calls TEllipse.Draw
      Figure.Destroy;
    end;

Only virtual and dynamic methods can be overridden. All methods, however, can be overloaded; see Overloading methods.

Final Methods

The Delphi compiler also supports the concept of final virtual and dynamic methods. Declarations of final methods have the form:

function|procedure FunctionName; virtual|dynamic; final; 

Here the virtual|dynamic syntax (two keywords and the | pipe between them) is used to specify that one and only one of the virtual or dynamic keywords should be used. Meaningful is only the virtual or dynamic keyword; the pipe symbol itself should be deleted.

When the keyword final is applied to a virtual or dynamic method, no descendent class can override that method. Use of the final keyword is an important design decision that can help document how the class is intended to be used. It can also give the compiler hints that allow it to optimize the code it produces.

Note: The virtual or dynamic keywords must be written before the final keyword.

Example

type
  Base = class
    procedure TestProcedure; virtual;
    procedure TestFinalProcedure; virtual; final;
  end;

  Derived = class(Base)
    procedure TestProcedure; override;
       //Ill-formed: E2352 Cannot override a final method
    procedure TestFinalProcedure; override;
  end;

Virtual versus Dynamic

In Delphi for Win32, virtual and dynamic methods are semantically equivalent. However, they differ in the implementation of method-call dispatching at run time: virtual methods optimize for speed, while dynamic methods optimize for code size.

In general, virtual methods are the most efficient way to implement polymorphic behavior. Dynamic methods are useful when a base class declares many overridable methods that are inherited by many descendent classes in an application, but only occasionally overridden.

Note: Only use dynamic methods if there is a clear, observable benefit. Generally, use virtual methods.

Overriding versus Hiding

If a method declaration specifies the same method identifier and parameter signature as an inherited method, but does not include override, the new declaration merely hides the inherited one without overriding it. Both methods exist in the descendent class, where the method name is statically bound. For example:

 type
    T1 = class(TObject)
       procedure Act; virtual;
    end;
 
    T2 = class(T1)
       procedure Act;   // Act is redeclared, but not overridden
    end;
 
 var
    SomeObject: T1;
 
 begin
    SomeObject := T2.Create;
    SomeObject.Act;    // calls T1.Act
 end;

Reintroduce

The reintroduce directive suppresses compiler warnings about hiding previously declared virtual methods. For example:

 procedure DoSomething; reintroduce; // The ancestor class also 
                                     // has a DoSomething method

Use reintroduce when you want to hide an inherited virtual method with a new one.

Abstract Methods

An abstract method is a virtual or dynamic method that has no implementation in the class where it is declared. Its implementation is deferred to a descendent class. Abstract methods must be declared with the directive abstract after virtual or dynamic. For example:

 procedure DoSomething; virtual; abstract;

You can call an abstract method only in a class or instance of a class in which the method has been overridden.

Class Methods

Most methods are called instance methods, because they operate on an individual instance of an object. A class method is a method (other than a constructor) that operates on classes instead of objects. There are two types of class methods: ordinary class methods and class static methods.

Ordinary Class Methods

The definition of a class method must begin with the reserved word class. For example:

 type
   TFigure = class
   public
      class function Supports(Operation: string): Boolean; virtual;
      class procedure GetInfo(var Info: TFigureInfo); virtual;
      ...
   end;

The defining declaration of a class method must also begin with class. For example:

 class procedure TFigure.GetInfo(var Info: TFigureInfo);
 begin
     ...
 end;

In the defining declaration of a class method, the identifier Self represents the class where the method is called (which can be a descendant of the class in which it is defined.) If the method is called in the class C, then Self is of the type class of C. Thus you cannot use Self to access instance fields, instance properties, and normal (object) methods. You can use Self to call constructors and other class methods, or to access class properties and class fields.

A class method can be called through a class reference or an object reference. When it is called through an object reference, the class of the object becomes the value of Self.

Class Static Methods

Like class methods, class static methods can be accessed without an object reference. Unlike ordinary class methods, class static methods have no Self parameter at all. They also cannot access any instance members. (They still have access to class fields, class properties, and class methods.) Also unlike class methods, class static methods cannot be declared virtual.

Methods are made class static by appending the word static to their declaration, for example:

 type
    TMyClass = class
      strict private
        class var
          FX: Integer;
 
      strict protected
        // Note: Accessors for class properties
        // must be declared class static.
        class function GetX: Integer; static;
        class procedure SetX(val: Integer); static;
 
      public
        class property X: Integer read GetX write SetX;
        class procedure StatProc(s: String); static;
    end;

Like a class method, you can call a class static method through the class type (for example, without having an object reference), such as:

 TMyClass.X := 17;
 TMyClass.StatProc('Hello');

Overloading Methods

A method can be redeclared using the overload directive. In this case, if the redeclared method has a different parameter signature from its ancestor, it overloads the inherited method without hiding it. Calling the method in a descendent class activates whichever implementation matches the parameters in the call.

If you overload a virtual method, use the reintroduce directive when you redeclare it in descendent classes. For example:

 type
   T1 = class(TObject)
     procedure Test(I: Integer); overload; virtual;
   end;
 
   T2 = class(T1)
     procedure Test(S: string); reintroduce; overload;
   end;
   ...
 
 SomeObject := T2.Create;
 SomeObject.Test('Hello!');       // calls T2.Test
 SomeObject.Test(7);              // calls T1.Test

Within a class, you cannot publish multiple overloaded methods with the same name. Maintenance of run time type information requires a unique name for each published member:

 type
     TSomeClass = class
       published
         function Func(P: Integer): Integer;
         function Func(P: Boolean): Integer;   // error
           ...

Methods that serve as property read or write specifiers cannot be overloaded.

The implementation of an overloaded method must repeat the parameter list from the class declaration. For more information about overloading, see Overloading Procedures and Functions in Procedures and Functions (Delphi).

Constructors

A constructor is a special method that creates and initializes instance objects. The declaration of a constructor looks like a procedure declaration, but it begins with the word constructor. Examples:

 constructor Create;
 constructor Create(AOwner: TComponent);

Constructors must use the default register calling convention. Although the declaration specifies no return value, a constructor returns a reference to the object it creates or is called in.

A class can have more than one constructor, but most have only one. It is conventional to call the constructor Create.

To create an object, call the constructor method on a class type. For example:

 MyObject := TMyClass.Create;

This allocates storage for the new object, sets the values of all ordinal fields to zero, assigns nil to all pointer and class-type fields, and makes all string fields empty. Other actions specified in the constructor implementation are performed next; typically, objects are initialized based on values passed as parameters to the constructor. Finally, the constructor returns a reference to the newly allocated and initialized object. The type of the returned value is the same as the class type specified in the constructor call.

If an exception is raised during the execution of a constructor that was invoked on a class reference, the Destroy destructor is automatically called to destroy the unfinished object.

When a constructor is called using an object reference (rather than a class reference), it does not create an object. Instead, the constructor operates on the specified object, executing only the statements in the constructor's implementation, and then returns a reference to the object. A constructor is typically invoked on an object reference in conjunction with the reserved word inherited to execute an inherited constructor.

Here is an example of a class type and its constructor:

  type
    TShape = class(TGraphicControl)
      private
        FPen: TPen;
        FBrush: TBrush;
        procedure PenChanged(Sender: TObject);
        procedure BrushChanged(Sender: TObject);
      public
        constructor Create(Owner: TComponent); override;
        destructor Destroy; override;
        ...
    end;
 
 constructor TShape.Create(Owner: TComponent);
 begin
     inherited Create(Owner);     // Initialize inherited parts
     Width := 65;          // Change inherited properties
     Height := 65;
     FPen := TPen.Create;  // Initialize new fields
     FPen.OnChange := PenChanged;
     FBrush := TBrush.Create;
     FBrush.OnChange := BrushChanged;
 end;

The first action of a constructor is usually to call an inherited constructor to initialize the object's inherited fields. The constructor then initializes the fields introduced in the descendent class. Because a constructor always clears the storage it allocates for a new object, all fields start with a value of zero (ordinal types), nil (pointer and class types), empty (string types), or Unassigned (variants). Hence there is no need to initialize fields in a constructor's implementation except to nonzero or nonempty values.

When invoked through a class-type identifier, a constructor declared virtual is equivalent to a static constructor. When combined with class-reference types, however, virtual constructors allow polymorphic construction of objects--that is, construction of objects whose types are not known at compile time. (See Class References.)

Destructors

A destructor is a special method that destroys the object where it is called and deallocates its memory. The declaration of a destructor looks like a procedure declaration, but it begins with the word destructor. Example:

 destructor SpecialDestructor(SaveData: Boolean);
 destructor Destroy; override;

Destructors on Win32 must use the default register calling convention. Although a class can have more than one destructor, it is recommended that each class override the inherited Destroy method and declare no other destructors.

To call a destructor, you must reference an instance object. For example:

 MyObject.Destroy;

When a destructor is called, actions specified in the destructor implementation are performed first. Typically, these consist of destroying any embedded objects and freeing resources that were allocated by the object. Then the storage that was allocated for the object is disposed of.

Here is an example of a destructor implementation:

 destructor TShape.Destroy;
 begin
     FBrush.Free;
     FPen.Free;
     inherited Destroy;
 end;

The last action in a destructor implementation is typically to call the inherited destructor to destroy the inherited fields of the object.

When an exception is raised during the creation of an object, Destroy is automatically called to dispose of the unfinished object. This means that Destroy must be prepared to dispose of partially constructed objects. Because a constructor sets the fields of a new object to zero or empty values before performing other actions, class-type and pointer-type fields in a partially constructed object are always nil. A destructor should therefore check for nil values before operating on class-type or pointer-type fields. Calling the Free method (defined in TObject) rather than Destroy offers a convenient way to check for nil values before destroying an object.

Class Constructors

A class constructor is a special class method that is not accessible to developers. Calls to class constructors are inserted automatically by the compiler into the initialization section of the unit where the class is defined. Normally, class constructors are used to initialize the static fields of the class or to perform a type of initialization, which is required before the class or any class instance can function properly. Even though the same result can be obtained by placing class initialization code into the initialization section, class constructors have the benefit of helping the compiler decide which classes should be included into the final binary file and which should be removed from it.

The next example shows the usual way of initializing class fields:

 type
   TBox = class
   private
     class var FList: TList<Integer>;
   end;
 
 implementation
 
 initialization
   { Initialize the static FList member }
   TBox.FList := TList<Integer>.Create();
 
 end.

This method has a big disadvantage: even though an application can include the unit in which TBox is declared, it may never actually use the TBox class. In the current example, the TBox class is included into the resulting binary, because it is referenced in the initialization section. To alleviate this problem, consider using class constructors:

 type
   TBox = class
   private
     class var FList: TList<Integer>;
     class constructor Create;
   end;
 
 implementation
 
 class constructor TBox.Create;
 begin
   { Initialize the static FList member }
   FList := TList<Integer>.Create();
 end;
 
 end.

In this case, the compiler checks whether TBox is actually used anywhere in the application, and if it is used, a call to the class constructor is added automatically to the initialization section of the unit.

Note: Even though the compiler takes care of ordering the initialization of classes, in some complex scenarios, ordering may become random. This happens when the class constructor of a class depends on the state of another class that, in turn, depends on the first class.

Note: The class constructor for a generic class or record may execute multiple times. The exact number of times the class constructor is executed in this case depends on the number of specialized versions of the generic type. For example, the class constructor for a specialized TList<String> class may execute multiple times in the same application.

Class Destructors

Class destructors are the opposite of class constructors in that they perform the finalization of the class. Class destructors come with the same advantages as class constructors, except for finalization purposes.

The following example builds on the example shown in class constructors and introduces the finalization routine:

 type
   TBox = class
   private
     class var FList: TList<Integer>;
     class constructor Create;
     class destructor Destroy;
   end;
 
 implementation
 
 class constructor TBox.Create;
 begin
   { Initialize the static FList member }
   FList := TList<Integer>.Create();
 end;
 
 class destructor TBox.Destroy;
 begin
   { Finalize the static FList member }
   FList.Free;
 end;
 
 end.

Note: The class destructor for a generic class or record may execute multiple times. The exact number of times the class destructor is executed in this case depends on the number of specialized versions of the generic type. For example, the class destructor for a specialized TList<String> class may execute multiple times in the same application.

Message Methods

Message methods implement responses to dynamically dispatched messages. The message method syntax is supported on all platforms. VCL uses message methods to respond to Windows messages.

A message method is created by including the message directive in a method declaration, followed by an integer constant from 1 through 49151 that specifies the message ID. For message methods in VCL controls, the integer constant can be one of the Win32 message IDs defined, along with corresponding record types, in the Messages unit. A message method must be a procedure that takes a single var parameter.

For example:

 type
     TTextBox = class(TCustomControl)
       private
        procedure WMChar(var Message: TWMChar); message WM_CHAR;
        ...
     end;

A message method does not have to include the override directive to override an inherited message method. In fact, it does not have to specify the same method name or parameter type as the method it overrides. The message ID alone determines to which message the method responds and whether it is an override.

Implementing Message Methods

The implementation of a message method can call the inherited message method, as in the following example:

 procedure TTextBox.WMChar(var Message: TWMChar);
 begin
    if Message.CharCode = Ord(#13) then
       ProcessEnter
    else
       inherited;
 end;

The inherited statement searches backward through the class hierarchy and invokes the first message method with the same ID as the current method, automatically passing the message record to it. If no ancestor class implements a message method for the given ID, inherited calls the DefaultHandler method originally defined in TObject.

The implementation of DefaultHandler in TObject simply returns without performing any actions. By overriding DefaultHandler, a class can implement its own default handling of messages. On Win32, the DefaultHandler method for controls calls the Win32 API DefWindowProc.

Message Dispatching

Message handlers are seldom called directly. Instead, messages are dispatched to an object using the Dispatch method inherited from TObject:

 procedure Dispatch(var Message);

The Message parameter passed to Dispatch must be a record whose first entry is a field of type Word containing a message ID.

Dispatch searches backward through the class hierarchy (starting from the class of the object where it is called) and invokes the first message method for the ID passed to it. If no message method is found for the given ID, Dispatch calls DefaultHandler.

See Also