Как указать переопределенный метод какого базового класса для вызова в Delphi? - PullRequest
3 голосов
/ 05 марта 2012

Как указать переопределенный метод, какой базовый класс вызывать в Delphi?

Допустим, строка наследования выглядит так: TObject -> ... SomeMoreBaseClass ... -> ParentClass -> MyClass

Предположим, что ParentClass не имеет Create (), но имеет Create (int = 0).Так что, когда вы вызываете ParentClass.Create (), он фактически вызывает ParentClass.Create (0)

Теперь, в конструкторе Create () MyClass, если я вызываю «унаследованный», я обнаружил, что яя не получаю 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 ]

12 голосов
/ 05 марта 2012

Прежде всего, как объясняет @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;

Я прочитал документацию для унаследованного ключевого слова, пытаясь понять разницу между inherited и inherited Create. Лучшие подсказки содержатся в следующих выдержках:

Если за унаследованным следует имя члена, это представляет обычный вызов метода ...

и

Когда унаследовано не имеет идентификатора после него, оно ссылается на унаследованный метод с тем же именем, что и метод вложения. В этом случае унаследованный не принимает явных параметров, но передает унаследованному методу те же параметры, с которыми был вызван включающий метод.

Кажется, это указывает на то, что два конкурирующих использования 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 очень удобочитаема и лаконична, она просто приводит к путанице, когда перегруженные методы находятся в игре.

6 голосов
/ 05 марта 2012

Прежде всего, это плохой пример: TDictionary.Create не является виртуальным конструктором, поэтому вы не на самом деле переопределяете его.Вы просто заново вводите это в новом классе.На самом деле это хорошо, потому что вы можете использовать трюки для вызова не виртуального метода из любого базового класса, который вы хотите.Вы можете просто использовать что-то вроде этого:

TBaseClass(Self).NonVirtualMethodName(Parameters).

или в вашем случае:

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

Конструктор можно вызывать как обычный метод, и Delphi позволяетэто по замыслу.Тем не менее, конструкторы не являются специальными методами.Даже если вы можете использовать метод приведения для вызова любого конструктора, который вам нужен, вы не должны этого делать: он «ломает ООП»: что, если ваш непосредственный родитель зависит от того, что происходит в его собственном конструкторе?Вы не должны знать или беспокоиться о том, что делает класс-предок в своем конструкторе.

Я упоминал, что 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.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...