Objektinstanzen als Funktionsrückgabe
Vielleicht haben Sie auch schon Funktionen geschrieben, die eine Objektinstanz zurückgeben. So etwas könnte z.B. so aussehen:
function TMyClass.GetNames: TStrings; var List: TStringlist; begin List := TStringlist.Create; //Liste beispielhaft befüllen List.Add('Heinz'); List.Add('Hans'); List.Add('August'); Result := List; end;
Mein persönlicher Rat lautet: Tun Sie das nicht!
Was ist daran schlecht?
Es lauert immer unterschwellig die Gefahr, Speicherlecks zu produzieren. Das krasseste Beispiel, das mir bislang in den Foren untergekommen ist, ging ungefähr so (die aufgerufene Methode soll die oben gezeigte sein):
procedure TMyClass.VerarbeiteNames; var i: integer; begin for i := 0 to GetNames.Count - 1 do Stringverarbeitung(GetNames[i]); end;
Das ist syntaktisch vollkommen in Ordnung, aber raten Sie einmal, wieviele TStrings-Instanzen in diesem Stückchen Code angelegt werden. Falls Sie es nicht wissen: es sind Listenanzahl + 1 Instanzen, welche nirgends freigegeben werden (können). Um Letzteres zu gewährleisten, hätte die Methode so aussehen müssen:
procedure TMyClass.VerarbeiteNames; var List: TStringlist; i: integer; begin List := GetNames; try for i := 0 to List.Count - 1 do Stringverarbeitung(List[i]); finally List.Free; end; end;
Das ist schon besser, hat aber immer noch einen Nachteil: die Freigabe erfolgt auf einer anderen Ebene als die Instanzierung. Es ist somit nicht auf den ersten Blick zu erkennen, was da im finally eigentlich freigegeben wird, da ja nirgends ein sichtbarer Konstruktor-Aufruf erfolgt ist. Den würde man wohl erst beim schrittweisen Ausführen im Debugger ausfindig machen können.
Wie anders (=besser) machen?
Machen Sie aus der Funktion eine Prozedur, welche die zu bearbeitende Instanz als Parameter entgegennimmt.
procedure TMyClass.GetNames(List: TStrings); begin Assert(Assigned(List)); List.BeginUpdate; try List.Add('Heinz'); List.Add('Hans'); List.Add('August'); finally List.EndUpdate; end; end;
Damit erreichen Sie, dass die Instanz außerhalb angelegt werden muss, Sie verlagern somit die Ebene.
procedure TMyClass.VerarbeiteNames; var i: integer; List: TStringlist; begin //Anlegen der Instanz... List := TStringlist.Create; try GetNames(List); for i := 0 to List.Count - 1 do Stringverarbeitung(List[i]); finally //... und Freigabe List.Free; end; end;
Eine Benutzung der Methode wie im ersten Negativbeispiel ist somit ausgeschlossen (zumindest wüsste ich spontan nicht, wie man das hinbekommen sollte).
Ausnahmen
Lazy Initialization
Es gibt ja auch Programmierer, die mit Speicher geizen. Bei Verwendung folgender Klasse
type TMyClass = class private FNames: TStrings; function GetNames: TStrings; procedure SetNames(const NewNames: TStrings); public destructor Destroy; override; property Names: TStrings read GetNames write SetNames; end; ... implementation destructor TMyClass.Destroy; begin FNames.Free; inherited; end; function TMyClass.GetNames: TStrings; begin if not Assigned(FNames) then FNames := TStringlist.Create; Result := FNames; end; procedure TMyClass.SetNames(const NewNames: TStrings); begin Names.Assign(NewNames); end;
wird die TStrings-Instanz FNames nur dann angelegt, wenn irgendwo auf die Property Names zugegriffen wird. Davon mag man halten, was man will, möglich ist es zumindest. Beachten Sie bitte: die Instanz wird entweder gar nicht oder höchstens einmalig erzeugt und dann FNames zugewiesen. Dies geschieht lediglich intern, so dass die Gefahr, Speicherlecks zu produzieren, hier nicht gegeben ist.
Funktion erzeugt die Instanz überhaupt nicht
Wir dürfen bei alledem nicht außer Acht lassen, dass die oben gezeigten Funktionen die zurückgegebene Instanz selbst anlegen. Das muss aber ja nicht zwingend der Fall sein. Angenommen, Sie schreiben ein mehrsprachiges Programm, das Schlüsselwörter in je einer Stringliste pro Sprache enthält. Je nachdem, welche Sprache gerade eingestellt ist, soll die Property Keywords die passende Liste bereitstellen.
type TTranslate = class private FLanguage: TLanguage; FEnglish: TStrings; FFrench: TStrings; FGerman: TStrings; FRussian: TStrings; function GetKeywords: TStrings; public property Keywords: TStrings read GetKeywords; property Language: TLanguage read FLanguage write FLanguage; end; ... implementation function TTranslate.GetKeywords: TStrings; begin case FLanguage of lgEnglish: Result := FEnglish; lgFrench: Result := FFrench; lgGerman: Result := FGerman; lgRussian: Result := FRussian; else raise Exception.Create('Invalid Language'); end; end;
Hier geht das völlig in Ordnung, da die Instanzen ja im Konstruktor (oder spätestens im Getter, s.o.) angelegt werden und nicht in der Funktion selbst. Instanzierung und Freigabe erfolgen somit auf derselben Ebene.