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

use*_*190 3 delphi inheritance constructor

如何指定在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;
Run Code Online (Sandbox Code Playgroud)

Dav*_*nan 12

首先,正如@Cosmin详细解释的那样,这个问题并不涉及被覆盖的方法.问题是关于调用继承的方法.

inherited Create;
Run Code Online (Sandbox Code Playgroud)

是你在这里做的最好的.这将调用TDictionary<TKey,TValue>构造函数传递默认ACapacity0.

实际上甚至可能更喜欢写:

inherited Create(0);
Run Code Online (Sandbox Code Playgroud)

并且非常明确.


我的假设是您的代码如下所示:

type
  TMyClass<TKey,TValue> = class(TDictionary<TKey,TValue>)
  public
    constructor Create;
  end;

constructor TMyClass<K,V>.Create;
begin
  inherited;
end;
Run Code Online (Sandbox Code Playgroud)

我读的文档,企图了解的差额继承的关键字inheritedinherited Create.最佳线索包含在以下摘录中:

如果继承后跟成员名称,则表示正常的方法调用...

继承后面没有标识符时,它引用与封闭方法同名的继承方法.在这种情况下,inherited不使用显式参数,而是将与调用封闭方法相同的参数传递给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
Run Code Online (Sandbox Code Playgroud)

那么无参数构造函数的实现就是:

constructor TDictionary<TKey,TValue>.Create;
begin
  Create(0);
end;
Run Code Online (Sandbox Code Playgroud)

如果做出这个决定,那么声明的无参数构造函数TObject将从任何派生类中隐藏,并且您的代码将按预期工作.

您在这里遇到的问题是涉及重载,默认参数,无参数构造函数TObject和构造函数的古怪语法的事件的不愉快汇合的结果inherited.虽然写作inherited具有高度的可读性和简洁性,但当重载方法发挥作用时,它只会导致混淆.

  • +1简洁地解释了简单地调用`inherited`和调用`inherited Create`之间的区别. (2认同)

Cos*_*und 6

首先,这是一个不好的例子:TDictionary.Create不是虚拟构造函数,所以你实际上并没有覆盖它.你只是在新课程中重新介绍它.这实际上是一件好事,因为您可以使用技巧从您想要的任何基类调用非虚方法.你可以简单地使用这样的东西:

TBaseClass(Self).NonVirtualMethodName(Parameters).
Run Code Online (Sandbox Code Playgroud)

或者在你的情况下:

constructor TMyDerived.Create;
begin
  TDictionary<T>(Self).Create; // cast and call the constructor you want.
end;
Run Code Online (Sandbox Code Playgroud)

构造函数可以作为普通方法调用,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;
Run Code Online (Sandbox Code Playgroud)

这是演示控制台应用程序,它演示了虚拟和非虚拟方法之间的区别以及如何调用继承的方法:

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.
Run Code Online (Sandbox Code Playgroud)