Наследование интерфейса Delphi: Почему я не могу получить доступ к элементам интерфейса предка?

Предположим, что у вас есть следующее:

//Note the original example I posted didn't reproduce the problem so
//I created an clean example  
  type
    IParent = interface(IInterface)
    ['{85A340FA-D5E5-4F37-ABDD-A75A7B3B494C}']
      procedure DoSomething;
    end;

    IChild = interface(IParent)
    ['{15927C56-8CDA-4122-8ECB-920948027015}']
      procedure DoSomethingElse;
    end;

    TGrandParent = class(TInterfacedObject)
    end;

    TParent = class(TGrandParent)
    end;

    TChild = class(TParent, IChild)
    private
      FChildDelegate: IChild;
    public
      property ChildDelegate:IChild read FChildDelegate implements IChild;
    end;

    TChildDelegate = class(TInterfacedObject, IChild)
    public
      procedure DoSomething;
      procedure DoSomethingElse;
    end;

Я бы подумал, что это позволит вам вызвать DoSomething, но это, похоже, не так:

procedure CallDoSomething(Parent: TParent);
begin
  if Parent is TChild then
    TChild(Parent).DoSomething;
end;

Ясно, что компилятор обеспечивает наследование интерфейса, потому что ни один класс не будет компилироваться, если не будут выполнены члены IParent. Несмотря на это, компилятор не может разрешить члены IParent при создании и использовании класса.

Я могу обойти это, явно включив IParent в объявление класса TMyClass:

TMyClass = class(TInterfacedObject, IChild, IParent)

Nevermind, это ничего не работает.

Ответ 1

Проблема заключается не в объявлениях интерфейсов или реализации классов, а в вашем потребительском коде:

procedure CallDoSomething(Parent: TParent);
begin
  if Parent is TChild then
    TChild(Parent).DoSomething;  // << This is wrong
end;

Не будет работать, потому что TChild не имеет метода " DoSomething". Если TChild реализован IChild напрямую, это обычно возможно, потому что TChild будет реализовывать метод напрямую И как часть интерфейса IChild.

Обратите внимание, что если TChild реализовано DoSomething в области PRIVATE, он останется доступным через интерфейс, но нормальные правила определения области видимости будут означать, что вы все равно не могли его вызывать (вне класса /uni ), используя ссылку TChild.

В вашем случае вам просто нужно получить соответствующий интерфейс, а затем вызвать метод, который вам нужен через интерфейс:

  if Parent is TChild then
    (Parent as IChild).DoSomething;

Однако вы используете тест типа класса, чтобы определить (вывести) наличие интерфейса, опираясь на деталь реализации (знание, что TChild реализует IChild). Я предлагаю вам вместо этого напрямую использовать тестирование интерфейса, чтобы изолировать эту зависимость от этих деталей реализации:

  var
    parentAsChild: IChild;

  begin
    if Parent.GetInterface(IChild, parentAsChild) then
      parentAsChild.DoSomething;
  end;

Ответ 2

Если класс реализации не объявляет, что он поддерживает наследуемый интерфейс, то класс не будет присвоить совместимость с переменными унаследованного интерфейса. Выбранный вами образец кода должен работать нормально (с использованием интерфейса IChild), но если вы попытаетесь назначить из экземпляра TMyClass переменной IParent, то у вас возникнут проблемы.

Причина в том, что COM и ActiveX позволяют реализации реализовать интерфейс потомства (ваш IChild), но запрещают предка этого интерфейса (IParent). Поскольку интерфейсы Delphi предназначены для совместимости с COM, что происходит с этим дурацким артефактом.

Я уверен, что написал статью об этом около 10 или 12 лет назад, но мой блог Borland не пережил переход к серверу Embarcadero.

Может быть директива компилятора изменить это поведение, я не помню.

Ответ 3

edit: Этот ответ больше не актуальен, поскольку он был опубликован до того, как был изменен исходный вопрос.


Это компилируется в Delphi 2010:

type
  IParent = interface(IInterface)
    function DoSomething: String;
  end;

  IChild = interface(IParent)
    function DoSomethingElse: string;
  end;

  TMyClass = class(TInterfacedObject, IChild)
  private
  public
    function DoSomething: String;
    function DoSomethingElse: String;
  end;

// ... 

procedure Test;
var
  MyObject : IChild;
begin
  MyObject := TMyClass.Create;
  MyObject.DoSomething;
end;

Ответ 4

Реализация Delphi QueryInterface не соответствует стандарту. В блоге, озаглавленном Как люди беспорядок IUnknown:: QueryInterface Раймонд Чен перечисляет общие неудачи в реализации. Наиболее примечательной является третья точка

Забыть отвечать на базовые интерфейсы. При реализации производного интерфейса вы неявно реализуете базовые интерфейсы, поэтому не забудьте также ответить на них.

 IShellView *psv = some object;
 IOleView *pow;
 psv->QueryInterface(IID_IOleView, (void**)&pow);

Некоторые объекты забываются, и QueryInterface терпит неудачу с E_NOINTERFACE.

Если унаследованный интерфейс явно не привязан к классу или к одному из его предков, Delphi не находит его. Он просто пересекает таблицу интерфейса объекта и его унаследованные типы и проверяет соответствие идентификаторов интерфейса, не проверяет базовые интерфейсы.