Methoden (Delphi)

Aus RAD Studio
Wechseln zu: Navigation, Suche

Nach oben zu Klassen und Objekte - Index

Eine Methode ist eine Prozedur oder Funktion, die zu einer bestimmten Klasse gehört. Daher wird beim Aufruf einer Methode das Objekt (bzw. bei einer Klassenmethode die Klasse) angegeben, mit dem die Operation durchgeführt werden soll. SomeObject.Free ruft beispielsweise die Methode Free in SomeObject auf.

In diesem Thema wird Folgendes behandelt:

  • Methodendeklarationen und -implementierungen
  • Methodenbindung
  • Methoden überladen
  • Konstruktoren und Destruktoren
  • Botschaftsmethoden

Allgemeines zu Methoden

Methoden werden in der Deklaration einer Klasse als Prozedur- und Funktionsköpfe angegeben, die wie Vorwärtsdeklarationen (forward) arbeiten. Die jeweilige Methode muss dann nach der Klassendefinition in demselben Modul durch eine definierende Deklaration implementiert werden. Die Deklaration der Klasse TMyClass im folgenden Beispiel enthält eine Methode mit dem Namen DoSomething:

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

Weiter unten in demselben Modul wird die definierende Deklaration für DoSomething implementiert:

 procedure TMyClass.DoSomething;
 begin
      ...
 end;

Während eine Klasse im interface- oder implementation-Abschnitt einer Unit deklariert werden kann, müssen die definierenden Deklarationen ihrer Methoden im implementation-Abschnitt stehen.

Im Kopf einer definierenden Deklaration wird der Name der Methode immer mit der Klasse qualifiziert, zu der die Methode gehört. Optional kann auch die Parameterliste aus der Klassendeklaration wiederholt werden, doch müssen in diesem Fall Reihenfolge, Typ und Namen der Parameter genau übereinstimmen. Bei einer Funktion muss auch der Rückgabewert identisch sein.

Methodendeklarationen können spezielle Direktiven enthalten, die in anderen Funktionen oder Prozeduren nicht verwendet werden. Direktiven müssen in der Klassendeklaration enthalten sein (nicht in der definierenden Deklaration) und in der folgenden Reihenfolge angegeben werden:

reintroduce; overload; Bindung; Aufrufkonvention; abstract; Warnung

Beschreibung:

  • Bindung ist virtual, dynamic oder override.
  • Die Aufrufkonvention lautet register, pascal, cdecl, stdcall oder safecall;
  • Warnung ist platform, deprecated oder library. Weitere Informationen zu diesen Warnungs-Direktiven finden Sie unter Hinweis-Direktiven.

Alle Delphi-Direktiven sind unter Direktiven aufgelistet.

Inherited

Das reservierte Wort inherited ist für die Implementierung von polymorphem Verhalten von großer Bedeutung. Es kann in Methodendefinitionen (mit oder ohne nachfolgendem Bezeichner) angegeben werden.

Folgt auf inherited ein Member-Name, entspricht dies einem normalen Methodenaufruf bzw. einer Referenz auf eine Eigenschaft oder ein Feld. Der einzige Unterschied besteht darin, dass die Suche nach dem referenzierten Member bei dem direkten Vorfahren der Klasse beginnt, zu der die Methode gehört. Wenn Sie beispielsweise

 inherited Create(...);

in der Definition einer Methode angeben, wird die geerbte Methode Create aufgerufen.

inherited ohne Bezeichner verweist auf die geerbte Methode mit demselben Namen wie die aufrufende Methode. Handelt es sich bei der aufrufenden Methode um eine Botschaftsbehandlung, verweist diese auf die geerbte Botschaftsbehandlung für dieselbe Botschaft. In diesem Fall übernimmt inherited keine expliziten Parameter, sondern der geerbten Methode werden einfach die Parameter der aufrufenden Methode übergeben. Zum Beispiel wird

 inherited;

häufig in der Implementierung von Konstruktoren verwendet. Der geerbte Konstruktor wird mit den Parametern aufgerufen, die an die abgeleitete Klasse übergeben wurden.

Self

Der Bezeichner Self verweist in der Implementierung einer Methode auf das Objekt, in dem die Methode aufgerufen wird. Das folgende Beispiel zeigt die Methode Add der Klasse TCollection in der Unit Classes:

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

Add ruft die Methode Create der Klasse auf, die das Feld FItemClass referenziert (ist immer ein Nachkomme von TCollectionItem). Da an TCollectionItem.Create ein einzelner Parameter des Typs TCollection übergeben wird, übergibt Add die Instanz von TCollection, in der Add aufgerufen wird. Der folgende Code veranschaulicht dies:

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

Self ist in vielen Situationen hilfreich. So kann beispielsweise ein Member-Bezeichner, der in einem Klassentyp deklariert ist, in einer Methode dieser Klasse erneut deklariert werden. In diesem Fall kann mit Self.Identifier auf den ursprünglichen Member-Bezeichner zugegriffen werden.

Informationen zu Self in Klassenmethoden finden Sie unter "Klassenoperatoren" in Klassenreferenzen.

Methodenbindung

Methodenbindungen können static (statisch, Standard), virtual (virtuell) oder dynamic (dynamisch) sein. Virtuelle und dynamische Methoden können überschrieben werden, und sie können abstrakt sein. Diese Angaben spielen eine Rolle, wenn eine Variable eines bestimmten Klassentyps eine Instanz einer abgeleiteten Klasse enthält. Sie bestimmen dann, welche Implementierung beim Aufruf der Methode aktiviert wird.

Statische Methoden

Methoden sind standardmäßig statisch. Beim Aufruf bestimmt der deklarierte Typ (also der Typ zur Compilierzeit) der im Aufruf verwendeten Klassen- bzw. Objektvariable, welche Implementierung aktiviert wird. Die Draw-Methoden im folgenden Beispiel sind statisch:

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

Ausgehend von diesen Deklarationen zeigt das folgende Beispiel, wie sich Aufrufe statischer Methoden auswirken. Im zweiten Aufruf von Figure.Draw referenziert die Variable Figure ein Objekt der Klasse TRectangle. Es wird jedoch die Draw-Implementierung in TFigure aufgerufen, weil Figure als TFigure deklariert ist:

 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;

Virtuelle und dynamische Methoden

Mithilfe der Direktiven virtual und dynamic können Methoden als virtual oder dynamic deklariert werden. Virtuelle und dynamische Methoden können im Gegensatz zu statischen Methoden in abgeleiteten Klassen überschrieben werden. Beim Aufrufen einer überschriebenen Methode bestimmt nicht der deklarierte, sondern der aktuelle Typ (also der Typ zur Laufzeit) der im Aufruf verwendeten Klassen- bzw. Objektvariable, welche Implementierung aktiviert wird.

Um eine Methode zu überschreiben, wird sie mit der Direktive override erneut deklariert. Bei einer override-Deklaration müssen Reihenfolge und Typ der Parameter sowie der Typ des Rückgabewertes (falls vorhanden) mit der Deklaration in der Vorfahrklasse übereinstimmen.

Im folgenden Beispiel wird die in der Klasse TFigure deklarierte Methode Draw in zwei abgeleiteten Klassen überschrieben:

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

Ausgehend von diesen Deklarationen zeigt der folgende Programmcode, wie sich der Aufruf einer virtuellen Methode durch eine Variable auswirkt, deren aktueller Typ zur Laufzeit geändert wird:

 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;

Nur virtuelle und dynamische Methoden können überschrieben werden. Alle Methoden können jedoch überladen werden (siehe Methoden überladen.

Finale Methoden

Der Delphi-Compiler unterstützt auch das Konzept der finalen virtuellen und dynamischen Methoden. Deklarationen der finalen Methoden haben die Form:

function|procedure FunctionName; virtual|dynamic; final; 

Hier wird mit der Syntax virtual|dynamic (zwei Schlüsselwörter und das Pipe-Zeichen | dazwischen) festgelegt, dass nur eins der Schlüsselwörter virtual oder dynamic verwendet werden sollte. Nur die Schlüsselwörter virtual oder dynamic sind bedeutend; das Pipe-Zeichen selbst sollte gelöscht werden.

Durch das Schlüsselwort final bei einer virtuellen oder dynamischen Methode kann verhindert werden, dass diese von einer abgeleiteten Klasse überschrieben wird. Mit dem Schlüsselwort final wird gleichzeitig dokumentiert, auf welche Weise die Klasse verwendet werden soll. Außerdem ermöglicht es dem Compiler eine Optimierung des generierten Codes.

Hinweis: Die Schlüsselwörter virtual oder dynamic müssen vor dem Schlüsselwort final geschrieben werden.

Beispiel

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;

Unterschiede zwischen virtuellen und dynamischen Methoden

In Delphi für Win32 sind virtuelle und dynamische Methoden von der Semantik her identisch. Sie unterscheiden sich aber bei der Implementierung der Aufrufweiterleitung zur Laufzeit: Virtuelle Methoden werden hinsichtlich der Geschwindigkeit, dynamische Methoden hinsichtlich der Codegröße optimiert.

Im Allgemeinen kann mit virtuellen Methoden polymorphes Verhalten am effizientesten implementiert werden. Dynamische Methoden sind hilfreich, wenn in einer Basisklasse eine große Anzahl überschreibbarer Methoden deklariert ist, die von vielen abgeleiteten Klassen geerbt, aber nur selten überschrieben werden.

Hinweis: Verwenden Sie dynamische Methoden nur, wenn sich daraus ein nachweisbarer Nutzen ergibt. Im Allgemein sollten Sie eher virtuelle Methoden verwenden.

Unterschiede zwischen Überschreiben und Verdecken

Wenn in einer Methodendeklaration dieselbe Bezeichner- und Parametersignatur wie bei einer geerbten Methode ohne die Anweisung override angegeben wird, wird die geerbte Methode durch die neue Deklaration nur verdeckt, nicht überschrieben. Beide Methoden sind jedoch in der abgeleiteten Klasse vorhanden, in der der Methodenname statisch gebunden wird. Zum Beispiel:

 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

Die Direktive reintroduce unterdrückt Compiler-Warnungen, wenn zuvor deklarierte virtuelle Methoden verdeckt werden. Zum Beispiel:

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

Verwenden Sie reintroduce, wenn eine geerbte virtuelle Methode durch eine neue Deklaration verdeckt werden soll.

Abstrakte Methoden

Eine abstrakte Methode ist eine virtuelle oder dynamische Methode, die nicht in der Klasse implementiert ist, in der sie deklariert wird. Die Implementierung wird erst später in einer abgeleiteten Klasse durchgeführt. Bei der Deklaration abstrakter Methoden muss die Anweisung abstract nach virtual oder dynamic angegeben werden. Zum Beispiel:

 procedure DoSomething; virtual; abstract;

Eine abstrakte Methode kann nur in einer Klasse oder Instanz einer Klasse aufgerufen werden, in der sie überschrieben wurde.

Klassenmethoden

Die meisten Methoden werden Instanzmethoden genannt, weil sie mit einer einzelnen Instanz eines Objekts arbeiten. Eine Klassenmethode ist eine Methode, die nicht mit Objekten, sondern mit Klassen arbeitet. Es gibt zwei Typen von Klassenmethoden: reguläre Klassenmethoden und klassenstatische Methoden.

Reguläre Klassenmethoden

Die Definition muss mit dem reservierten Wort class beginnen. Zum Beispiel:

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

Auch die definierende Deklaration einer Klassenmethode muss mit class eingeleitet werden. Zum Beispiel:

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

In der definierenden Deklaration einer Klassenmethode kann mit dem Bezeichner Self auf die Klasse zugegriffen werden, in der die Methode aufgerufen wird (dies kann auch ein Nachkomme der Klasse sein, in der sie definiert ist). Wird die Methode beispielsweise in der Klasse C aufgerufen, hat Self den Typ class of C. Daher können mit Self nicht auf Instanzfelder, Instanzeigenschaften und normale (Objekt-)Methoden zugreifen, sondern Self kann nur für Aufrufe von Konstruktoren und anderen Klassenmethoden oder für den Zugriff auf Klasseneigenschaften und Klassenfelder verwendet werden.

Eine Klassenmethode kann über eine Klassenreferenz oder eine Objektreferenz aufgerufen werden. Bei einer Objektreferenz erhält Self als Wert die Klasse des betreffenden Objekts.

Klassenstatische Methoden

Auf klassenstatische Methoden kann, wie auf Klassenmethoden, ohne Objektreferenz zugegriffen werden. Im Gegensatz zu regulären Klassenmethoden haben klassenstatische Methoden keinen Self-Parameter. Sie können außerdem auf keine Instanz-Member zugreifen. (Sie können aber auf Klassenfelder, Klasseneigenschaften und Klassenmethoden zugreifen.) Wiederum im Gegensatz zu Klassenmethoden können klassenstatische Methoden nicht als virtual deklariert werden.

Um eine Methode als klassenstatisch zu deklarieren, fügen Sie das Wort static an die Deklaration an:

 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;

Wie eine Klassenmethode kann eine klassenstatische Methode über den Klassentyp (also ohne Objektreferenz) aufgerufen werden, z. B.:

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

Methoden überladen

Eine Methode kann mit der Direktive overload neu deklariert werden. Wenn sich die Parametersignatur von der ihres Vorfahren unterscheidet, wird die geerbte Methode überladen, ohne dass sie dadurch verdeckt wird. Bei einem Aufruf der Methode in einer abgeleiteten Klasse wird dann diejenige Implementierung aktiviert, bei der die Parameter übereinstimmen.

Verwenden Sie beim Überladen einer virtuellen Methode die Direktive reintroduce, wenn die Methode in einer abgeleiteten Klasse neu deklariert wird. Zum Beispiel:

 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

Innerhalb einer Klasse dürfen nicht mehrere überladene Methoden mit demselben Namen als published deklariert werden. Zur Pflege von Laufzeit-Typinformationen wird für jeden als published deklarierten Member ein eindeutiger Name benötigt:

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

Methoden, die als read- oder write-Bezeichner für Eigenschaften dienen, können nicht überladen werden.

Bei der Implementierung einer überladenen Methode muss die Parameterliste aus der Klassendeklaration wiederholt werden. Weitere Informationen zum Überladen finden Sie unter Prozeduren und Funktionen überladen in Prozeduren und Funktionen (Delphi).

Konstruktoren

Ein Konstruktor ist eine spezielle Methode, mit der Instanzobjekte erstellt und initialisiert werden. Die Deklaration gleicht einer normalen Prozedurdeklaration, beginnt aber mit dem Wort constructor. Beispiele:

 constructor Create;
 constructor Create(AOwner: TComponent);

Für Konstruktoren muss die Standard-Aufrufkonvention register verwendet werden. Obwohl die Deklaration keinen Rückgabewert enthält, gibt ein Konstruktor immer einen Verweis auf das Objekt zurück, das er erstellt bzw. in dem er aufgerufen wird.

Eine Klasse kann auch mehrere Konstruktoren haben. Im Normalfall ist jedoch nur einer vorhanden. Konstruktoren heißen normalerweise immer Create.

Das folgende Beispiel zeigt, wie Sie ein Objekt durch einen Aufruf des Konstruktors eines Klassentyps erstellen können:

 MyObject := TMyClass.Create;

Diese Anweisung reserviert zuerst Speicher für das neue Objekt. Anschließend wird allen Ordinalfeldern der Wert null, allen Zeigern und Klassentypfeldern der Wert nil und allen String-Feldern ein leerer String zugewiesen. Danach werden die weiteren Aktionen in der Implementierung des Konstruktors ausgeführt (z. B. Initialisieren der Objekte mit den als Parameter übergebenen Werten). Am Ende gibt der Konstruktor eine Referenz auf das neu erstellte und initialisierte Objekt zurück. Der Typ entspricht dem im Aufruf angegebenen Klassentyp.

Tritt in einem mit einer Klassenreferenz aufgerufenen Konstruktor eine Exception auf, wird das unvollständige Objekt automatisch durch einen Aufruf des Destruktors Destroy freigegeben.

Wenn Sie einen Konstruktor mit einer Objektreferenz (anstatt mit einer Klassenreferenz) aufrufen, wird kein Objekt erstellt. Stattdessen werden, wie bei einer normalen Routine, die angegebenen Anweisungen mit dem Objekt ausgeführt, und es wird eine Referenz auf das Objekt zurückgegeben. Beim Aufruf mit einer Objektreferenz wird normalerweise der geerbte Konstruktor mit inherited ausgeführt.

Das folgende Beispiel zeigt einen Klassentyp und den zugehörigen Konstruktor:

  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;

Als erste Anweisung wird in der Regel immer der geerbte Konstruktor aufgerufen, um die geerbten Felder zu initialisieren. Danach werden den in der abgeleiteten Klasse deklarierten Feldern Werte zugewiesen. Da der Konstruktor grundsätzlich den Speicherbereich bereinigt, der dem neuen Objekt zugewiesen wird, erhalten alle Felder automatisch den Anfangswert null (Ordinaltypen), nil (Zeiger und Klassentypen), einen leeren String (String-Typen) oder Unassigned (Varianten). Aus diesem Grund brauchen nur solche Felder explizit initialisiert zu werden, denen ein Anfangswert ungleich null (bzw. kein leerer String) zugewiesen werden soll.

Ein als virtual deklarierter Konstruktor, der mit einem Klassentypbezeichner aufgerufen wird, entspricht einem statischen Konstruktor. In Verbindung mit Klassenreferenztypen können jedoch durch virtuelle Konstruktoren Objekte polymorph erstellt werden (d. h. der Objekttyp ist beim Compilieren noch nicht bekannt). Weitere Informationen finden Sie unter Klassenreferenzen.

Destruktoren

Ein Destruktor ist eine spezielle Methode, die ein Objekt im Speicher freigibt. Die Deklaration gleicht einer Prozedurdeklaration, beginnt aber mit dem Wort destructor. Beispiel:

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

In Win32 muss für Destruktoren die Standard-Aufrufkonvention register verwendet werden. Obwohl in einer Klasse mehrere Destruktoren implementiert werden können, ist es ratsam, nur die geerbte Methode Destroy zu überschreiben und keine weiteren Destruktoren zu deklarieren.

Ein Destruktor kann nur über ein Instanzobjekt aufgerufen werden. Zum Beispiel:

 MyObject.Destroy;

Beim Aufruf eines Destruktors werden zuerst die in der Implementierung angegebenen Aktionen ausgeführt. Normalerweise werden hier untergeordnete Objekte und zugewiesene Ressourcen freigegeben. Danach wird der durch das Objekt belegte Speicherplatz freigegeben.

Das folgende Beispiel zeigt eine typische Destruktorimplementierung:

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

Die letzte Anweisung ruft den geerbten Destruktor auf, der die geerbten Felder freigibt.

Wenn beim Erstellen eines Objekts eine Exception auftritt, wird das unvollständige Objekt automatisch durch einen Aufruf von Destroy freigegeben. Destroy muss daher auch in der Lage sein, Objekte freizugeben, die nur teilweise erstellt wurden. Da im Konstruktor alle Felder eines neuen Objekts zuerst mit null oder leeren Werten initialisiert werden, haben Klassen- und Zeigerfelder in einer unvollständigen Instanz immer den Wert nil. Testen Sie solche Felder im Destruktor immer auf den Wert nil, bevor Sie Operationen mit ihnen durchführen. Wenn Sie Objekte nicht mit Destroy, sondern mit der Methode Free (in TObject definiert) freigeben, wird diese Prüfung automatisch durchgeführt.

Klassenkonstruktoren

Ein Klassenkonstruktor ist eine spezielle Klassenmethode, auf die von Entwicklern nicht zugegriffen werden kann. Aufrufe von Klassenkonstruktoren werden automatisch vom Compiler in den initialization-Abschnitt der Unit eingefügt, in der die Klasse definiert ist. In der Regel werden mit Klassenkonstruktoren die statischen Felder der Klasse initialisiert oder Initialisierungen ausgeführt, die erforderlich sind, damit die Klasse oder eine Klasseninstanz ordnungsgemäß arbeiten kann. Dasselbe Ergebnis kann zwar auch durch Einfügen des Klasseninitialisierungscodes in den initialization-Abschnitt erzielt werden, aber Klassenkonstruktoren unterstützen den Compiler bei der Entscheidung, welche Klassen in die endgültige Binärdatei aufgenommen und welche daraus entfernt werden sollen.

Das nächste Beispiel zeigt eine normale Initialisierung von Klassenfeldern:

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

Dieses Vorgehen hat einen entscheidenden Nachteil: Auch wenn eine Anwendung die Unit einbeziehen kann, in der TBox deklariert ist, kann es sein, dass die Klasse TBox tatsächlich nie verwendet wird. Im obigen Beispiel wird die Klasse TBox in die resultierende Binärdatei einbezogen, weil sie im initialization-Abschnitt referenziert wird. Mit Klassenkonstruktoren kann dieses Problem umgangen werden:

 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 diesem Beispiel überprüft der Compiler, ob TBox tatsächlich in der Anwendung verwendet wird. Falls ja, wird automatisch ein Aufruf des Klassenkonstruktors dem initialization-Abschnitt der Unit hinzugefügt.

Hinweis: In der Regel sorgt der Compiler für die richtige Reihenfolge der Initialisierung von Klassen, in einigen komplexen Szenarien könnte die Reihenfolge aber zufällig werden. Und zwar dann, wenn der Klassenkonstruktor einer Klasse vom Status einer anderen Klasse abhängig ist, der wiederum von der ersten Klasse abhängt.

Hinweis: Der Klassenkonstruktor für eine generische Klasse oder einen Record kann mehrfach ausgeführt werden. Die genaue Anzahl der Ausführungen des Klassenkonstruktors hängt in diesem Fall von der Anzahl der spezialisierten Versionen des generischen Typs ab. Der Klassenkonstruktor für eine spezialisierte TList<String>-Klasse kann beispielsweise mehrfach in derselben Anwendung ausgeführt werden.

Klassendestruktoren

Klassendestruktoren sind das Gegenteil von Klassenkonstruktoren, und zwar weil sie die Finalisierung der Klasse vornehmen. Klassendestruktoren bieten dieselben Vorteile wie Klassenkonstruktoren, nur dass sie die Finalisierung vornehmen.

Das folgende Beispiel baut auf dem Beispiel für Klassenkonstruktoren auf und führt die Finalisierungsroutine ein:

 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.

Hinweis: Der Klassendestruktor für eine generische Klasse oder einen Record kann mehrfach ausgeführt werden. Die genaue Anzahl der Ausführungen des Klassendestruktors hängt in diesem Fall von der Anzahl der spezialisierten Versionen des generischen Typs ab. Der Klassendestruktor für eine spezialisierte TList<String>-Klasse kann beispielsweise mehrfach in derselben Anwendung ausgeführt werden.

Botschaftsmethoden

In Botschaftsmethoden können Reaktionen auf dynamisch weitergeleitete Botschaften implementiert werden. Die Syntax für Botschaftsmethoden wird auf allen Plattformen unterstützt. In der VCL werden Botschaftsmethoden verwendet, um auf Windows-Botschaften zu antworten.

Sie erstellen eine Botschaftsmethode, indem Sie die Direktive message gefolgt von einer Integer-Konstante von 1 bis 49151 (der sogenannten Botschafts-ID) in eine Methodendeklaration aufnehmen. In Botschaftsmethoden für VCL-Steuerelemente kann als Integer-Konstante eine der Botschafts-IDs von Win32 verwendet werden, die (zusammen mit den entsprechenden Record-Typen) in der Unit Messages definiert sind. Eine Botschaftsmethode muss eine Prozedur mit einem einzelnen var-Parameter sein.

Zum Beispiel:

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

In einer Botschaftsmethode muss die Direktive override nicht angegeben werden, um eine geerbte Botschaftsmethode zu überschreiben. Es muss nicht einmal derselbe Methodenname oder Parametertyp wie bei der zu überschreibenden Methode verwendet werden. Allein die Botschafts-ID bestimmt, auf welche Botschaft die Methode reagiert, und ob die Methode überschrieben wird.

Botschaftsmethoden implementieren

In der Implementierung einer Botschaftsmethode kann die geerbte Botschaftsmethode wie im folgenden Beispiel aufgerufen werden:

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

Die Anweisung inherited durchsucht die Klassenhierarchie nach oben und ruft die erste Botschaftsmethode mit derselben ID wie die aktuelle Methode auf. Dabei wird automatisch der Botschafts-Record übergeben. Ist in keiner Vorfahrklasse eine Botschaftsmethode mit dieser ID implementiert, ruft inherited die ursprünglich in TObject definierte Methode DefaultHandler auf.

Die Implementierung von DefaultHandler in TObject gibt einfach die Steuerung zurück, ohne eine Aktion auszuführen. Durch Überschreiben von DefaultHandler kann in einer Klasse eine eigene Standardbehandlung für Botschaften implementiert werden. In Win32 ruft die DefaultHandler-Methode für Steuerelemente die DefWindowProc-Funktion der Win32-API auf.

Botschaftsweiterleitung

Botschaftsmethoden werden normalerweise nicht direkt aufgerufen. Stattdessen werden Botschaften mithilfe der von TObject geerbten Methode Dispatch an ein Objekt weitergeleitet:

 procedure Dispatch(var Message);

Der an Dispatch übergebene Parameter Message muss ein Record sein, dessen erstes Element ein Word-Feld mit einer Botschafts-ID enthält.

Dispatch durchsucht die Klassenhierarchie nach oben (beginnend bei der Klasse des Objekts, in dem sie aufgerufen wird) und ruft die erste für die übergebene ID gefundene Botschaftsmethode auf. Wird keine solche Methode gefunden, ruft Dispatch die Methode DefaultHandler auf.

Siehe auch