Если у меня есть следующие интерфейсы и класс, который их реализует -
IBase = Interface ['{82F1F81A-A408-448B-A194-DCED9A7E4FF7}']
End;
IDerived = Interface(IBase) ['{A0313EBE-C50D-4857-B324-8C0670C8252A}']
End;
TImplementation = Class(TInterfacedObject, IDerived)
End;
Следующий код выводит «Bad!» -
Procedure Test;
Var
A : IDerived;
Begin
A := TImplementation.Create As IDerived;
If Supports (A, IBase) Then
WriteLn ('Good!')
Else
WriteLn ('Bad!');
End;
Это немного раздражает, но понятно. Поддержка не может быть приведена к IBase, потому что IBase отсутствует в списке GUID, которые поддерживает TImplementation. Это можно исправить, изменив объявление на -
TImplementation = Class(TInterfacedObject, IDerived, IBase)
Но даже без этого я уже знаю , что A реализует IBase, потому что A - это IDerived, а IDerived - это IBase. Поэтому, если я пропущу проверку, я могу разыграть A, и все будет хорошо -
Procedure Test;
Var
A : IDerived;
B : IBase;
Begin
A := TImplementation.Create As IDerived;
B := IBase(A);
//Can now successfully call any of B's methods
End;
Но мы сталкиваемся с проблемой, когда начинаем помещать IBases в общий контейнер - например, TInterfaceList. Он может содержать только IInterfaces, поэтому мы должны выполнить некоторое приведение.
Procedure Test2;
Var
A : IDerived;
B : IBase;
List : TInterfaceList;
Begin
A := TImplementation.Create As IDerived;
B := IBase(A);
List := TInterfaceList.Create;
List.Add(IInterface(B));
Assert (Supports (List[0], IBase)); //This assertion fails
IBase(List[0]).DoWhatever; //Assuming I declared DoWhatever in IBase, this works fine, but it is not type-safe
List.Free;
End;
Мне бы очень хотелось иметь какое-то утверждение для перехвата любых несовпадающих типов - такого рода вещи можно сделать с объектами с помощью оператора Is, но это не работает для интерфейсов. По разным причинам я не хочу явно добавлять IBase в список поддерживаемых интерфейсов. Можно ли как-нибудь написать TImplementation и утверждение таким образом, чтобы оно оценивалось как истинное, если бы надежное выполнение IBase (List [0]) было безопасно?
Edit:
Как указывалось в одном из ответов, я добавляю две основные причины, по которым я не хочу добавлять IBase в список интерфейсов, реализуемых TImplementation.
Во-первых, это на самом деле не решает проблему. Если в Test2 выражение:
Supports (List[0], IBase)
возвращает true, это не значит, что безопасно выполнять hard-cast. QueryInterface может возвращать другой указатель для удовлетворения запрошенного интерфейса. Например, если TImplementation явно реализует и IBase, и IDerived (и IInterface), то утверждение пройдет успешно:
Assert (Supports (List[0], IBase)); //Passes, List[0] does implement IBase
Но представьте, что кто-то по ошибке добавляет элемент в список как IInterface
List.Add(Item As IInterface);
Утверждение все еще проходит - элемент все еще реализует IBase, но ссылка, добавленная в список, является только IInterface - жесткое приведение его к IBase не даст ничего разумного, поэтому утверждение недостаточно для проверки того, следующий жесткий бросок безопасен. Единственный способ, который гарантированно сработает, - это использовать as-cast или support:
(List[0] As IBase).DoWhatever;
Но это удручающее снижение производительности, так как предполагается, что код будет отвечать за добавление элементов в список, чтобы убедиться, что они относятся к типу IBase - мы должны быть в состоянии это предположить (отсюда и утверждение, что это предположение неверно). Утверждение даже не требуется, за исключением того, что выявляете более поздние ошибки, если кто-то меняет некоторые типы. Исходный код, из-за которого возникает эта проблема, также довольно критичен к производительности, поэтому я бы предпочел избегать затрат на производительность, которые достигают небольшого уровня (он по-прежнему только отлавливает несовпадающие типы во время выполнения, но без возможности компилировать более быструю сборку выпуска) .
Вторая причина - я хочу иметь возможность сравнивать ссылки на равенство, но этого нельзя сделать, если один и тот же объект реализации содержится в разных ссылках с разными смещениями VMT.
Редактировать 2: Расширено вышеописанное редактирование с примером.
Редактировать 3: Примечание. Вопрос в том, как сформулировать утверждение так, чтобы жесткое приведение было безопасным при прохождении утверждения, а не как избежать его. Существуют способы по-другому выполнить сложный этап или полностью его избежать, но если есть затраты производительности во время выполнения, я не могу их использовать. Я хочу, чтобы все расходы были проверены в утверждении, чтобы его можно было скомпилировать позже.
Сказав это, если кто-то может избежать проблемы вообще без затрат на производительность и без опасности проверки типов, это было бы здорово!