如何指定在Delphi中调用哪个基类的重写方法?

时间:2012-03-05 06:47:07

标签: delphi constructor inheritance

如何指定在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;

2 个答案:

答案 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,试图了解inheritedinherited 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不是虚拟构造函数,这就是原因。调用虚拟和非虚拟方法的方式存在根本区别。通过“虚方法表”调用虚方法,您将始终获得实际实例化的对象的方法。非虚方法在编译时解析。在以下示例中,您将注意到XY都使用相同的对象类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.