2009-11-08 3 views
9

Was ist die Best Practice für die Rückgabe einfacher Objekte aus Funktionen/Prozeduren in Delphi?Objekt als Ergebnis von func/proc in Delphi erhalten

z. 2 Arten von Code:

Pass erstellte Objekt als Referenz, bevölkern Objekt in Proc, zerstören sie danach

procedure Proc(var Obj: TMyObject); 
begin 
    // populate Obj 
end; 

O := TMyObject.Create; 
try 
    Proc(O); 
    // manipulate populated object 
finally 
    O.Free; 
end; 

oder erhalten Objekt als Ergebnis von Funktion erstellt, zerstören nach der Manipulation

function Func: TMyObj; 
begin 
    Result := TMyObj.Create; 
end; 

O := Func; 
if O <> nil then 
begin 
    try 
    // manipulate 
    finally 
    O.Free; 
    end; 
end; 

Antwort

8

Es gibt keine Best Practice. Die primäre Sache, die Sie tun sollten, ist jedoch sicherzustellen, dass immer klar ist, wer für die Zerstörung des Objekts zu einem bestimmten Zeitpunkt verantwortlich ist, selbst wenn eine Ausnahme auftritt.

Es ist nichts falsch daran, dass eine Funktion eine neue Instanz erstellt und zurückgibt. Eine solche Funktion ist ein factory. Sie können es wie den Konstruktor einer Klasse behandeln, also sollten Sie sicherstellen, dass es sich wie ein Konstruktor verhält: Entweder ein gültiges Objekt zurückgeben oder eine Ausnahme auslösen. Es gibt niemals eine Nullreferenz zurück.

function Func: TMyObj; 
begin 
    Result := TMyObj.Create; 
    try 
    Result.X := Y; 
    except 
    Result.Free; 
    raise; 
    end; 
end; 

Das ist eine Ausnahmebehandlung Muster, das Sie nicht sehr oft sehen, aber es ist für diese Art der Funktion wichtig. Rückgabe des Objekts überträgt Eigentum von der Funktion an den Aufrufer, aber nur wenn es schafft, vollständig auszuführen. Wenn es aufgrund einer Ausnahme vorzeitig verlassen muss, wird das Objekt freigegeben, da der Aufrufer keine Möglichkeit hat, es selbst zu löschen. (Funktionen, die auf eine Ausnahme beenden aufgrund nicht über Rückgabewerte). Der Anrufer es wie folgt verwenden:

O := Func; 
try 
    writeln(O.X); 
finally 
    O.Free; 
end; 

Wenn es eine Ausnahme in Func dann O nie zugewiesen wird, so gibt es nichts für den Anrufer frei.


Wenn der Anrufer das Objekt erstellt und Sie geben es an einem anderen Funktion, es zu initialisieren, ein „var“ Parameter die Parameter nicht machen. Das stellt dem Aufrufer bestimmte Beschränkungen auf, die eine Variable genau des Typs verwenden müssen, der von der Funktion angefordert wird, selbst wenn stattdessen ein Abkömmlingstyp erstellt wurde.

Eine solche Funktion sollte das Objekt nicht freigeben. Der Aufrufer gibt den aufgerufenen Funktionen keine Eigentumsrechte zu, insbesondere wenn er das Objekt nach der Rückgabe der Funktion verwenden möchte.

+0

Dank für das Erhalten mich gerade :) große Antwort –

4

Es hängt von der Lebensdauer des Objekts und davon ab, wer dafür verantwortlich ist. Die meisten Zeitobjekte sollten von derselben Entität erstellt und zerstört werden.

Angenommen, Ihre Methode füllt eine TStringList mit Ergebnissen aus der Analyse einer Datei. Soll diese Funktion die TStringList erstellen, oder sollten Sie sie erstellen und als Referenz weitergeben?

Ich finde es lesbarer, es zu erstellen, als Referenz übergeben und später zerstören, alle in aufeinanderfolgenden Zeilen des Codes.

Jetzt betrachten wir, dass Sie eine Funktion haben, die einen TCustomer für jeden hinzugefügten Kunden zurückgibt. In diesem Fall würde ich eine Funktion verwenden, weil ich annehme, dass meine Entität eine Liste oder etwas von Kunden haben würde, die dafür verantwortlich sind, sie zu zerstören, wenn sie nicht benötigt werden.

-3

I verwenden oft das Konstrukt

FUNCTION SomeFunction(SL : TStrings = NIL) : TStrings; 
    BEGIN 
    IF Assigned(SL) THEN Result:=SL ELSE Result:=TStringList.Create; 
    // Use Result for the remainder of the function 
    END; 

So kann ich es sowohl als ein Verfahren mit einer übergebenen in Bezug, und als eine Funktion verwenden, welche die Instanz selbst erzeugt.

+2

-1 Ich finde Ihre Praxis sehr verwirrend. Ich würde lieber zwei Methoden erstellen (eine, die die andere aufruft, nachdem ich das Objekt erstellt habe) und den Namen der Methode klar ausdrücken lassen, was vor sich geht. Was ist, wenn Sie 'SomeFunction (Obj)' aufrufen wollen, wenn 'Obj' ein Parameter Ihrer eigenen Methode ist? Sie können nicht wissen, ob es "nil" ist oder nicht, so dass Sie nicht wissen können, ob Sie das zurückgegebene Objekt freigeben müssen oder nicht ... – jpfollenius

+0

Ich verstehe Ihren Kommentar nicht "Was ist, wenn Sie SomeFunction (Obj) wenn Obj ein Parameter deiner eigenen Methode ist?Du kannst nicht wissen, ob es Nil ist oder nicht, also kannst du nicht wissen, ob du das zurückgegebene Objekt freigeben musst oder nicht ... ". Was meinst du mit" wenn Obj ein Parameter deiner eigenen Methode ist? "?? ? Wenn Sie mit einem Codebeispiel arbeiten sollten, dann werde ich es Ihnen gerne erklären ... – HeartWare

+0

Die Sache ist, dass Sie Methoden auf eine Weise schreiben sollten, dass jeder einen einzigen Zweck erfüllt, nicht zu tun mehr Operationen in einer Methode, deshalb ist Ihre Lösung verwirrend und nicht geradlinig –

2

Meine Regel ist Besitz und Schöpfung insgesamt zu haben. Ich habe immer den Schöpfer als Besitzer und damit die Verantwortung, das Objekt zu zerstören. Die Erstellung des Objekts ist im Aufrufcode explizit, es ist nie ein Nebeneffekt des Aufrufs.

So sind die üblichen Signaturen meiner Funktionen sind

function Func(o:tMyO): TMyO; 
begin 
    // .... 
    Result := o; 
end; 

diese Weise, die ich kann entweder tun

o := func(TMyO.create); 

oder

o := TMyO.create; 
    // ... 
    func(o); 
3

Es ist ein gemeinsames Idiom Delphi den Anrufer zu lassen Erstellen Sie das Objekt und übergeben Sie es als Parameter. Beachten Sie, dass Sie es in fast allen Fällen nicht var deklarieren müssen.

procedure Proc (Obj : TMyObject) 
begin 
    Obj.SomeProperty := 'SomeValue'; 
    ... 
end; 

Telefonvorwahl:

Obj := TMyObject.Create; 
try 
    Proc (Obj); 
finally 
    FreeAndNil (Obj); 
end;  

Dies vermeidet Verwirrung darüber, wer das Objekt zu befreien hat. Beachten Sie, dass es bei einer Kette von Methodenaufrufen sehr schnell kompliziert werden kann, Objekte zu verfolgen, die irgendwo entlang der Linie freigegeben werden müssen.

Ein weiterer Nachteil: die Schaffung und Zerstörung im Code verstreut macht es unmöglich, try...finally Blöcke zu verwenden, die nur ein weiteres hilfreiches Idiom ist, Ressourcenverluste zu vermeiden.

Wenn Sie möchten, dass Ihre Methode das Objekt erstellt, würde ich es explizit im Funktionsnamen machen, etwas wie CreateAndInitializeList klingt richtig für mich.

1

Wie bereits erwähnt, sollte im Allgemeinen dieselbe Entität, die das Objekt erstellt hat, es freigeben, und das bedeutet, dass der Aufrufer die Objektreferenz erstellen sollte, anstatt sie innerhalb der Funktion auszuführen.

Dies ist jedoch nur möglich, wenn der Anrufer den genauen Typ des zurückzugebenden Elements kennt und nicht einen Supertyp. Zum Beispiel:

var E: TEmployee; 

E := CreateEmployee(EmployeeID); // Could return TEmployee or subclasses TManager or TRetiredEmployee 

try 

    E.SendEmail(MessageText); 
    if (E is TRetiredEmployee) then 
     E.PrintLetter; 

finally 

    E.Free; 

end; 

In Fällen wie diesen, finde ich es hilfreich ist, das Wort „Create“, oder eine andere Anzeige zu schließen, im Namen der Fabrik Funktion bin ich telefonieren.

Verwandte Themen