Сегодня я столкнулся с какой-то странной ситуацией.
У меня есть несколько интерфейсов и объектов. Код выглядит следующим образом:
IInterfaceZ = interface(IInterface)
['{DA003999-ADA2-47ED-A1E0-2572A00B6D75}']
procedure DoSomething;
end;
IInterfaceY = interface(IInterface)
['{55BF8A92-FCE4-447D-B58B-26CD9B344EA7}']
procedure DoNothing;
end;
TObjectB = class(TInterfacedObject, IInterfaceZ)
procedure DoSomething;
end;
TObjectC = class(TInterfacedObject, IInterfaceY)
public
FTest: string;
procedure DoNothing;
end;
TObjectA = class(TInterfacedObject, IInterfaceZ, IInterfaceY)
private
FInterfaceB: IInterfaceZ;
FObjectC: TObjectC;
function GetBB: IInterfaceZ;
public
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
property BB: IInterfaceZ read GetBB implements IInterfaceZ;
property CC: TObjectC read FObjectC implements IInterfaceY;
end;
procedure TObjectB.DoSomething;
begin
Sleep(1000);
end;
procedure TObjectA.AfterConstruction;
begin
inherited;
FInterfaceB := TObjectB.Create;
FObjectC := TObjectC.Create;
FObjectC.FTest := 'Testing';
end;
procedure TObjectA.BeforeDestruction;
begin
FreeAndNil(FObjectC);
FInterfaceB := nil;
inherited;
end;
function TObjectA.GetBB: IInterfaceZ;
begin
Result := FInterfaceB;
end;
procedure TObjectC.DoNothing;
begin
ShowMessage(FTest);
end;
Теперь, если я получаю доступ к таким различным реализациям, я получаю следующие результаты:
procedure TestInterfaces;
var
AA: TObjectA;
YY: IInterfaceY;
ZZ: IInterfaceZ;
NewYY: IInterfaceY;
begin
AA := TObjectA.Create;
// Make sure that the Supports doesn't kill the object.
// This line of code is necessary in XE2 but not in XE4
AA._AddRef;
// This will add one to the refcount for AA despite the fact
// that AA has delegated the implementation of IInterfaceY to
// to FObjectC.
Supports(AA, IInterfaceY, YY);
YY.DoNothing;
// This will add one to the refcount for FInterfaceB.
// This is also allowing a supports from a delegated interface
// to another delegated interface.
Supports(YY, IInterfaceZ, ZZ);
ZZ.DoSomething;
// This will fail because the underlying object is actually
// the object referenced by FInterfaceB.
Supports(ZZ, IInterfaceY, NewYY);
NewYY.DoNothing;
end;
Первый вызов Supports, который использует переменную в инструментах, возвращает YY, который на самом деле является ссылкой на TObjectA. Моя переменная АА считается подсчитанной. Поскольку базовый объект подсчитанных ссылок является TObjectA, вторая поддержка, которая использует интерфейс в вызове поддержки, работает и возвращает мне интерфейс. Основной объект фактически является TObjectB. Внутренний объект за FInterfaceB является объектом, подсчитанным ссылкой. Эта часть имеет смысл, потому что GetBB - это фактически FInterfaceB. Как и ожидалось здесь, последний вызов Supports возвращает null для NewYY, и вызов в конце не работает.
Мой вопрос в том, ссылается ли ссылка на TObjectA на первый вызов поддержки по дизайну? Другими словами, когда свойство, реализующее интерфейс, возвращает объект, а не интерфейс, означает ли это, что объектом владельца будет тот, который делает подсчет ссылок? Мне всегда казалось, что реализация также приведет к тому, что внутренний делегированный объект будет считаться ссылкой вместо основного объекта.
Объявления следующие:
property BB: IInterfaceZ read GetBB implements IInterfaceZ;
С помощью этой опции выше внутренний объект за FInterfaceB - это тот, который подсчитывается ссылкой.
property CC: TObjectC read FObjectC implements IInterfaceY;
С помощью этого второго варианта выше TObjectA - это тот, который подсчитывается ссылкой, а не делегированный объект FObjectC.
Это по дизайну?
Edit
Я просто тестировал это в XE2, и поведение отличается. Второй оператор Supports возвращает nil для ZZ. Отладчик в XE4 говорит мне, что YY имеет в виду (TObjectA как IInterfaceY). В XE2 он говорит мне, что его a (указатель как IInterfaceY). Кроме того, в XE2 AA не пересчитывается на первый оператор поддержки, но внутренний FObjectC подсчитывается ссылкой.
Дополнительная информация после ответа на вопрос
Есть одна оговорка. Вы можете связать версию интерфейса, но не версию объекта. Это означает, что что-то вроде этого будет работать:
TObjectBase = class(TInterfacedObject, IMyInterface)
…
end;
TObjectA = class(TInterfacedObject, IMyInterface)
FMyInterfaceBase: IMyInterface;
property MyDelegate: IMyInterface read GetMyInterface implements IMyInterface;
end;
function TObjectA.GetMyInterface: IMyInterface;
begin
result := FMyInterfaceBase;
end;
TObjectB = class(TInterfacedObject, IMyInterface)
FMyInterfaceA: IMyInterface;
function GetMyInterface2: IMyInterface;
property MyDelegate2: IMyInterface read GetMyInterface2 implements IMyInterface;
end;
function TObjectB.GetMyInterface2: IMyInterface;
begin
result := FMyInterfaceA;
end;
Но объектная версия дает ошибку компилятора с этим утверждением, что TObjectB не реализует методы для интерфейса.
TObjectBase = class(TInterfacedObject, IMyInterface)
…
end;
TObjectA = class(TInterfacedObject, IMyInterface)
FMyObjectBase: TMyObjectBase;
property MyDelegate: TMyObjectBase read FMyObjectBase implements IMyInterface;
end;
TObjectB = class(TInterfacedObject, IMyInterface)
FMyObjectA: TObjectA;
property MyDelegate2: TObjectA read FMyObjectA implements IMyInterface;
end;
Итак, если вы хотите начать цепочку делегирования, вам нужно придерживаться интерфейсов или работать с ним по-другому.