Наследование интерфейса 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, это ничего не работает.

4 ответа

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

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;


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

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

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

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


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;


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

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

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

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

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

licensed under cc by-sa 3.0 with attribution.