从delphi中的函数/过程返回简单对象的最佳实践是什么?
例如。 2种代码:
将创建的对象作为参考传递,在Proc中填充对象,然后将其销毁
procedure Proc(var Obj: TMyObject);
begin
// populate Obj
end;
O := TMyObject.Create;
try
Proc(O);
// manipulate populated object
finally
O.Free;
end;
或 从函数获取创建的对象,在操作后销毁
function Func: TMyObj;
begin
Result := TMyObj.Create;
end;
O := Func;
if O <> nil then
begin
try
// manipulate
finally
O.Free;
end;
end;
答案 0 :(得分:8)
没有最佳实践。但是,您应该做的主要事情是确保始终清楚谁在任何给定时间负责销毁对象,即使发生异常也是如此。
创建新实例并返回它的函数没有任何问题。这样的函数是factory。您可以像处理类的构造函数一样对待它,因此您应该确保它的行为类似于构造函数:返回有效对象或抛出异常。它永远不会返回空引用。
function Func: TMyObj;
begin
Result := TMyObj.Create;
try
Result.X := Y;
except
Result.Free;
raise;
end;
end;
这是一种你经常看不到的异常处理模式,但它对这种功能很重要。返回对象将所有权从函数转移到调用者,但前提是它设法完全执行。如果由于异常而必须提前离开,它会释放对象,因为调用者无法自行释放它。 (由于异常而终止的函数没有返回值。)调用者将使用它:
O := Func;
try
writeln(O.X);
finally
O.Free;
end;
如果Func
中存在例外,那么O
永远不会被分配,因此没有任何内容可供调用者免费使用。
当调用者创建对象并将其传递给另一个函数以初始化它时,不要将参数设为“var”参数。这对调用者有一些限制,调用者必须使用与函数请求的类型完全相同的变量,即使创建了一些后代类型。
这样的函数应该不释放对象。调用者不会对它调用的函数赋予所有权责任,特别是当它计划在函数返回后使用该对象时。
答案 1 :(得分:4)
这取决于对象的生命周期以及对谁负责。 大多数时间对象应该由同一个实体创建和销毁。
假设您的方法使用解析文件的结果填充TStringList。 你应该让这个函数创建TStringList,还是应该创建它并作为引用传递?
我发现创建它更具可读性,将其作为参考传递,然后在连续的代码行中进行销毁。
现在让我们考虑一下,为每个添加的客户提供一个返回TCustomer的函数。在这种情况下,我会使用一个函数,因为我认为我的实体会有一个列表或其他东西,负责在不需要时销毁它们。
答案 2 :(得分:3)
让调用者创建对象并将其作为参数传递是一种常见的Delphi习惯用法。请注意,几乎在所有情况下都不必声明它var
。
procedure Proc (Obj : TMyObject)
begin
Obj.SomeProperty := 'SomeValue';
...
end;
致电代码:
Obj := TMyObject.Create;
try
Proc (Obj);
finally
FreeAndNil (Obj);
end;
这避免了谁必须释放对象的混淆。请注意,如果您有一系列方法调用,那么跟踪需要在该行某处释放的对象可能会变得非常复杂。
还有一个缺点:代码中散布的创建和破坏使得无法使用try...finally
块,这是避免资源泄漏的另一个有用的习惯用法。
如果你想让你的方法创建对象,我会在函数名中明确它,CreateAndInitializeList
之类的东西听起来对我来说。
答案 3 :(得分:2)
我的规则是完全拥有所有权和创造权。我总是让创作者成为所有者,因此具有销毁对象的责任。对象的创建在调用代码中是显式的,它永远不会是调用的副作用。
所以我职能的通常签名是
function Func(o:tMyO): TMyO;
begin
// ....
Result := o;
end;
这样我可以做任何一次
o := func(TMyO.create);
或
o := TMyO.create;
// ...
func(o);
答案 4 :(得分:1)
如上所述,通常创建对象的同一实体应该释放它,这意味着调用者应该创建对象引用而不是在函数内部完成。
但是,只有在调用者知道要返回的项的确切类型而不是超类型时,才可能这样做。例如:
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;
在这种情况下,我发现在我正在调用的工厂函数的名称中包含“Create”或其他指示符这一词是有帮助的。
答案 5 :(得分:-3)
我经常使用构造
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;
这样,我既可以将它用作带有传入引用的PROCEDURE,也可以用作创建实例本身的FUNCTION。