在 Delphi 中引入 reintroduce
关键字的动机是什么?
如果您有一个子类包含一个与父类中虚函数同名的函数,且未使用 override 修饰符声明,则会导致编译错误。在这种情况下添加 reintroduce 修饰符可以解决错误,但我从未理解编译错误的原因。
在 Delphi 中引入 reintroduce
关键字的动机是什么?
如果您有一个子类包含一个与父类中虚函数同名的函数,且未使用 override 修饰符声明,则会导致编译错误。在这种情况下添加 reintroduce 修饰符可以解决错误,但我从未理解编译错误的原因。
重新引入(reintroduce)本质上就像是override,但它适用于非dynamic和非virtual方法,并且如果通过祖先类型的表达式访问对象实例,则不会替换行为。
更进一步的解释:
重新引入是一种向编译器传达意图的方式,即您没有犯错误。我们使用override关键字覆盖祖先中的方法,但它要求祖先方法是virtual或dynamic的,并且当以祖先类的形式访问对象时,您希望行为发生变化。现在介绍重新引入。它允许您告诉编译器,您并没有意外创建与虚拟或动态祖先方法同名的方法(如果编译器没有警告您,这将很麻烦)。
RTL使用reintroduce来隐藏继承的构造函数。例如,TComponent有一个带一个参数的构造函数。但是,TObject有一个无参构造函数。当实例化一个新的TComponent时,RTL希望您仅使用TComponent的带有一个参数的构造函数,而不是从TObject继承的无参构造函数。因此,它使用reintroduce来隐藏继承的构造函数。这种方式,reintroduce有点像在C#中将无参构造函数声明为私有。
TComponent.Create
中没有看到reintroduce
。 - Rob Kennedy重新引入修饰符的目的是为了防止常见的逻辑错误。
我将假设重新引入关键字如何修复警告是常识,并解释为什么会生成警告以及为什么将该关键字包含在语言中。考虑下面的Delphi代码;
TParent = Class
Public
Procedure Procedure1(I : Integer); Virtual;
Procedure Procedure2(I : Integer);
Procedure Procedure3(I : Integer); Virtual;
End;
TChild = Class(TParent)
Public
Procedure Procedure1(I : Integer);
Procedure Procedure2(I : Integer);
Procedure Procedure3(I : Integer); Override;
Procedure Setup(I : Integer);
End;
procedure TParent.Procedure1(I: Integer);
begin
WriteLn('TParent.Procedure1');
end;
procedure TParent.Procedure2(I: Integer);
begin
WriteLn('TParent.Procedure2');
end;
procedure TChild.Procedure1(I: Integer);
begin
WriteLn('TChild.Procedure1');
end;
procedure TChild.Procedure2(I: Integer);
begin
WriteLn('TChild.Procedure2');
end;
procedure TChild.Setup(I : Integer);
begin
WriteLn('TChild.Setup');
end;
Procedure Test;
Var
Child : TChild;
Parent : TParent;
Begin
Child := TChild.Create;
Child.Procedure1(1); // outputs TChild.Procedure1
Child.Procedure2(1); // outputs TChild.Procedure2
Parent := Child;
Parent.Procedure1(1); // outputs TParent.Procedure1
Parent.Procedure2(1); // outputs TParent.Procedure2
End;
给定上面的代码,TParent中的两个过程都被隐藏了。所谓隐藏是指这些过程不能通过TChild指针调用。编译代码示例会产生一个警告:
[DCC Warning] Project9.dpr(19): W1010 Method 'Procedure1' hides virtual method of base type 'TParent'
为什么只有虚函数会出现警告而其他函数不会呢?它们都被隐藏了。
Delphi的优点在于库设计者能够发布新版本而不用担心破坏现有客户端代码的逻辑。这与Java形成对比,因为类是隐式虚拟的,所以向库中的父类添加新函数充满危险。假设上述TParent存在于第三方库中,并且库制造商发布了下面的新版本。
// version 2.0
TParent = Class
Public
Procedure Procedure1(I : Integer); Virtual;
Procedure Procedure2(I : Integer);
Procedure Procedure3(I : Integer); Virtual;
Procedure Setup(I : Integer); Virtual;
End;
procedure TParent.Setup(I: Integer);
begin
// important code
end;
想象一下我们的客户端代码中有以下代码
Procedure TestClient;
Var
Child : TChild;
Begin
Child := TChild.Create;
Child.Setup;
End;
对于客户端来说,使用版本2或1的库编译代码并不重要,因为在任何情况下,TChild.Setup都会按照用户意愿进行调用。而在库中;
// library version 2.0
Procedure TestLibrary(Parent : TParent);
Begin
Parent.Setup;
End;
简而言之:试图覆盖非虚方法是没有意义的。添加关键字reintroduce
来承认您犯了一个错误。
TDescendant.MyMethod
会对TDescendants造成潜在的混淆,因为它会添加另一个同名方法,编译器会警告你。重新引入消除了这种歧义,并告诉编译器你知道要使用哪个方法。ADescendant.MyMethod
调用TDescendant的方法,(ADescendant as TAncestor).MyMethod
调用TAncestor的方法。总是如此!没有混淆...编译器很开心!首先,正如上面所说的,您绝不能有意地重新引入虚方法。重新引入的唯一合理用途是当祖先的作者(而不是您)添加了一个与您的后代冲突的方法,并且重命名您的后代方法不是一个选项时。其次,即使在您重新引入具有不同参数的虚方法的类中,您也可以轻松调用原始版本的虚方法:
type
tMyFooClass = class of tMyFoo;
tMyFoo = class
constructor Create; virtual;
end;
tMyFooDescendant = class(tMyFoo)
constructor Create(a: Integer); reintroduce;
end;
procedure .......
var
tmp: tMyFooClass;
begin
// Create tMyFooDescendant instance one way
tmp := tMyFooDescendant;
with tmp.Create do // please note no a: integer argument needed here
try
{ do something }
finally
free;
end;
// Create tMyFooDescendant instance the other way
with tMyFooDescendant.Create(20) do // a: integer argument IS needed here
try
{ do something }
finally
free;
end;
这是由于框架版本(包括VCL)而引入语言的。
如果您有现有的代码库,并且框架更新(例如因为您购买了更新的Delphi版本)引入了与代码库祖先中的方法同名的虚拟方法,那么reintroduce
将允许您摆脱W1010警告。
这是您唯一应该使用reintroduce
的地方。