Наследование интерфейса 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 не находит его. Он просто пересекает таблицу интерфейса объекта и его унаследованные типы и проверяет соответствие идентификаторов интерфейса, не проверяет базовые интерфейсы.