如何指定在Delphi中调用哪个基类的重写方法?
比方说,像这样的继承线: TObject - > ... SomeMoreBaseClass ... - > ParentClass - > MyClass的
假设ParentClass没有Create(),但它有一个Create(int = 0)。 这样当你调用ParentClass.Create()时,它实际上调用ParentClass.Create(0)
现在,在MyClass的构造函数Create()中,如果我调用“inherited;”,我发现我没有得到ParentClass.Create(0),而是得到基类的.Create()或者甚至TObject。
那么,如何让它调用ParentClass.Create()?
最简单的是“继承Create(0)”,但它感觉不够“正确”。
(在我的案例中,ParentClass实际上是System.Generics.Collections.TDictionary)
type
TParentClass = class
public
constructor Create(n:Integer = 0);
end;
TDerivedClass = class(TParentClass)
public
constructor Create; // Note: no parameters
end;
constructor TDerivedClass.Create;
begin
// inherited; // this calls TObject.Create, not TParentClass.Create(0);
inherited Create(0);
end;
答案 0 :(得分:12)
首先,正如@Cosmin详细解释的那样,该问题与重写的方法无关。问题是关于调用继承的方法。
inherited Create;
是你在这里做的最好的。这会调用TDictionary<TKey,TValue>
构造函数传递ACapacity
的默认0
。
事实上,甚至可能更愿意写:
inherited Create(0);
并且非常明确。
我的假设是您的代码如下所示:
type
TMyClass<TKey,TValue> = class(TDictionary<TKey,TValue>)
public
constructor Create;
end;
constructor TMyClass<K,V>.Create;
begin
inherited;
end;
我为继承的关键字阅读documentation,试图了解inherited
和inherited Create
之间的区别。最佳线索包含在以下摘录中:
如果继承后跟成员名称,则表示正常的方法调用...
和
当继承之后没有标识符时,它引用与封闭方法同名的继承方法。在这种情况下,inherited不接受显式参数,但将继承方法传递给调用封闭方法的相同参数。
这似乎暗示inherited
的两种竞争用途的处理方式不同。
我的理解是inherited
导致使用匹配参数调用构造函数。在您的情况下,TMyClass<K,V>.Create
是无参数的,因此唯一匹配的构造函数是TObject
的构造函数。请注意,TDictionary
的所有构造函数都不能匹配,因为它们都是参数。
另一方面,当你写inherited Create
时,这是一个正常的方法调用。因此可以将默认参数添加到方法调用中。关键的一点是,此变体允许使用不匹配的参数列表调用继承的方法。
在我看来,应该为虚拟方法保留没有以下标识符的inherited
语法。
TDictionary<TKey,TValue>
的设计师可以帮助你摆脱这种不幸的命运。 TDictionary<TKey,TValue>
的构造函数应该像这样实现:
constructor Create; overload;
constructor Create(ACapacity: Integer); overload;
.....other constructors omitted
然后,无参数构造函数的实现就是:
constructor TDictionary<TKey,TValue>.Create;
begin
Create(0);
end;
如果做出了这个决定,TObject
中声明的无参数构造函数将被隐藏在任何派生类中,并且您的代码可以按预期工作。
你在这里遇到的问题是由于涉及重载,默认参数,TObject
的无参数构造函数和构造函数的inherited
的古怪语法而导致的不愉快汇总的结果。虽然编写inherited
具有高度的可读性和简洁性,但当重载方法处于运行状态时,它只会导致混淆。
答案 1 :(得分:6)
首先,这是一个不好的例子:TDictionary.Create
不是虚拟构造函数,因此您实际不会覆盖它。你只是在新课程中重新介绍它。这实际上是一件好事,因为您可以使用技巧从您想要的任何基类调用非虚方法。你可以简单地使用这样的东西:
TBaseClass(Self).NonVirtualMethodName(Parameters).
或在你的情况下:
constructor TMyDerived.Create;
begin
TDictionary<T>(Self).Create; // cast and call the constructor you want.
end;
构造函数可以作为普通方法调用,Delphi允许这样设计。尽管如此,构造函数是特殊方法。即使你可以使用强制转换技巧来调用你想要的任何构造函数,你也不应该这样做:它“破坏了OOP”:如果你的直接父亲依赖于它自己的构造函数中发生的事情会怎么样?你不应该知道或关心祖先类在构造函数中做了什么。
我提到TDictionary.Create
不是虚拟构造函数,这就是原因。调用虚拟和非虚拟方法的方式存在根本区别。通过“虚方法表”调用虚方法,您将始终获得实际实例化的对象的方法。非虚方法在编译时解析。在以下示例中,您将注意到X
和Y
都使用相同的对象类TSecondChild
进行实例化,但在调用NonVirtual
方法时,结果会有所不同,具体取决于变量的类型。对VMethod
不是这样,这是一个虚方法,并且总是调用正确的方法。
这对虚拟构造函数有影响,因为您正在讨论构造函数。例如,如果你做这样的事情,你最终会得到一个无限的递归循环:
constructor TDemoClass.Create; // where Create is VIRTUAL, not the case with TDictionary
begin
// I'm "smart", I don't call Inherited
TBaseClass(Self).Create; // Ooops, I just called myself! Recursive loop!
end;
这是演示控制台应用程序,它演示了虚拟和非虚拟方法之间的区别以及如何调用继承的方法:
program Project13;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TBase = class
public
procedure NonVirtual;
procedure VMethod(N:Integer);virtual;
end;
TFirstChild = class(TBase)
public
procedure NonVirtual;
procedure VMethod(N:Integer);override;
end;
TSecondChild = class(TFirstChild)
public
procedure NonVirtual;
procedure VMethod(N:Integer);override;
end;
{ TBase }
procedure TBase.NonVirtual;
begin
WriteLn('TBase.NonVirtual');
end;
procedure TBase.VMethod(N:Integer);
begin
WriteLn('TBase.VMethod');
end;
{ TFirstChild }
procedure TFirstChild.NonVirtual;
begin
WriteLn('TFirstChild.NonVirtual');
end;
procedure TFirstChild.VMethod(N:Integer);
begin
WriteLn('TFirstChild.VMethod');
end;
{ TSecondChild }
procedure TSecondChild.NonVirtual;
begin
WriteLn('TSecondChild.NonVirtual');
TBase(Self).NonVirtual;
end;
procedure TSecondChild.VMethod(N:Integer);
begin
WriteLn('TSecondCHild.VMethod, N=', N);
if N > 0 then // This stops infinite recursion
TBase(Self).VMethod(N-1);
end;
var X: TFirstChild;
Y: TSecondChild;
begin
try
WriteLn('Calling through a variable of type TFirstChild');
X := TSecondChild.Create;
X.NonVirtual; // Writes TFirstChild.NonVirtual
X.VMethod(2); // Writes TSecondChild.NonVirtual, 3 times
WriteLn;
WriteLn('Calling through a variable of type TSecondChild');
Y := TSecondChild.Create;
Y.NonVirtual; // Writes TSecondChild.NonVirtual
Y.VMethod(2); // Writes TSecondChild.NonVirtual, 3 times
WriteLn;
WriteLn('Press ENTER');
Readln;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.