如何安全地绕过Delphi错误:“正式和实际参数的类型必须相同”

时间:2010-01-12 19:19:02

标签: delphi

我需要一种方法来编写一个通用过程来处理对象类型或其任何后代。

我的第一次尝试是宣布

procedure TotalDestroy(var obj:TMyObject);

但与后代对象一起使用时

type TMyNewerObject = class(TMyObject);
var someNewerObject: TMyNewerObject;

TotalDestroy(someNewerObject);

我得到臭名昭着的错误“正式和实际参数的类型必须相同”

因此,在寻找解决方案时,我查看了Delphi系统FreeAndNil过程的源代码。我发现了这个令人敬畏的声明,以及这个令人惊讶的评论

{ FreeAndNil frees the given TObject instance and 
  sets the variable reference to nil.  
  Be careful to only pass TObjects to this routine. }

procedure FreeAndNil(var Obj);

它避免了类型检查错误,但它没有使用安全网。

我的问题是......是否有任何安全的方法来检查无类型var参数的类型?

或换句话说,您是否可以改进此Delphi源代码以便不需要警告?

procedure FreeAndNil(var Obj);
var
  Temp: TObject;
begin
  Temp := TObject(Obj);
  Pointer(Obj) := nil;
  Temp.Free;
end;

4 个答案:

答案 0 :(得分:9)

让我们来看看你想做什么。

你想调用一个接受X的方法,传入一个Y类型的对象,其中Y是X的后代。障碍,参数是一个“var”参数。

让我们分析如果可能的话你可以做些什么。

type
    TBase = class
    end;
    TDescendant = class(TBase)
    end;

procedure Fiddle(var x: TBase);
begin
    x := TDescendant.Create;
end;

type
    TOtherDescendant = class(TBase)
    end;

var a: TOtherDescendant;
a := TOtherDescendant.Create;
Fiddle(a);

呃 - 哦,现在a不再包含TOtherDescendant的实例,它包含TDescendant的实例。这可能会让电话跟随的代码感到意外。

您不仅要考虑打算对您提出的语法做些什么,还要考虑 可以用语法做什么。

你应该阅读Eric Lipperts关于.NET中类似问题的优秀博客文章,在这里找到:Why do ref and out parameters not allow type variation?

答案 1 :(得分:7)

我以前写过这个,使用了与Lasse's非常相似的例子:

除非您正在为更改输入参数本身的值而不仅仅是其中一个属性编写赋值语句,否则您不应该首先通过引用传递参数。

如果 编写赋值语句来更改参数的值,那么编译器消息确实是真的,你应该注意它。

需要绕过错误的一个原因是当你编写像TApplication.CreateForm这样的函数时。它的工作是更改输入参数的值,并且新值的类型会有所不同,并且无法在编译时确定。如果您正在编写这样的函数,那么Delphi的唯一选择就是使用untyped var参数,然后调用者和接收者都要承担额外的负担,以确保一切顺利。调用者需要确保它传递一个变量,该变量能够保存函数将放入其中的任何类型的值,并且函数需要确保它存储与调用者请求的类型兼容的类型的值。

CreateForm的情况下,调用者传入类引用文字和该类类型的变量。该函数实例化类并将引用存储在变量中。

我对CreateFormFreeAndNil的评价不高,主要是因为他们的无类型参数牺牲了类型安全性的方式,以换取相对较少的额外便利。您尚未显示TotalDestroy函数的实现,但我怀疑其var参数最终将提供与其他两个函数相同的低效用。请参阅我的文章:

答案 2 :(得分:3)

除了Lasse所写的,这是非常正确的,大多数时候你不想将对象传递给 var 参数。

对象是引用类型。您所看到的对象实际上是对它的引用。如果要将对象更改为新对象,则只需要将对象引用传递给 var 参数。如果您只想修改对象的成员,那么只需将其传递给普通参数即可。使方法调用采用TMyObject参数而不是var TMyObject参数,它应该有效。

当然,如果你真的要替换这个对象,那么可以随意忽略这一切,并看看Lasse的回答。

答案 3 :(得分:1)

您可以改进此Delphi源代码,以便不再需要警告吗?

是的,您可以使用一种类型安全的方法来避免编译器错误。 在最新的Delphi 10.4 Sidney中,FreeAndNil过程已更改为:

procedure FreeAndNil(const [ref] Obj: TObject);
var
  Temp: TObject;
begin
  Temp := Obj;
  TObject(Pointer(@Obj)^) := nil;
  Temp.Free;
end;

它是对象的安全类型,例如在传递接口引用时会捕获错误。

通过const [ref]传递参数的方式意味着该参数是通过引用传递的。如果没有[ref]属性,则大小等于和小于指针的参数将按值传递。

在这里,即使将对象作为常量传递,引用也会被修改。 从这个意义上讲,这不是一个完美的声明,但是它将比以前的实现做得更好。

来自New features in Delphi 10.4

这意味着FreeAndNil的错误使用现在将导致编译器错误。过去,不会发现错误用法,从而导致难以解决的错误。请注意,尽管将参数声明为const,但确实修改了按引用变量。

使用FreeAndNil的这种声明,可能会出现新的但不那么糟糕的错误调用类:可以调用该方法,以传递属性或方法结果,以及类型类型隐式转换为TObject的方法结果等等。然后,零值将成为表达式中的临时变量。