Если у меня есть следующие интерфейсы и класс, который их реализует -
IBase = Interface ['{82F1F81A-A408-448B-A194-DCED9A7E4FF7}']
End;
IDerived = Interface(IBase) ['{A0313EBE-C50D-4857-B324-8C0670C8252A}']
End;
TImplementation = Class(TInterfacedObject, IDerived)
End;
Следующий код печатает "Плохо!" -
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. Так что, если я не буду проверять, я могу бросить А, и все будет хорошо -
Procedure Test;
Var
A : IDerived;
B : IBase;
Begin
A := TImplementation.Create As IDerived;
B := IBase(A);
//Can now successfully call any of B 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 и утверждение таким образом, что он будет оценивать true, если hard-casting IBase (List [0]) - это безопасная вещь?
Edit:
Как только он появился в одном из ответов, я добавляю две основные причины, по которым я не хочу добавлять IBase в список интерфейсов, реализуемых TImplementation.
Во-первых, это фактически не решает проблему. Если в Test2 выражение:
Supports (List[0], IBase)
возвращает true, это не означает, что безопасно выполнять жесткую печать. QueryInterface может возвращать другой указатель для удовлетворения запрошенного интерфейса. Например, если TImplementation явно реализует как IBase, так и IDerived (и IInterface), то это утверждение успешно пройдет:
Assert (Supports (List[0], IBase)); //Passes, List[0] does implement IBase
Но представьте, что кто-то ошибочно добавляет элемент в список как IInterface
List.Add(Item As IInterface);
Утверждение все еще проходит - элемент все еще реализует IBase, но ссылка, добавленная в список, является только интерфейсом - твердое преобразование ее в IBase не приведет к чему-либо разумному, поэтому этого утверждения недостаточно для проверки того, следование за твердым покрытием безопасно. Единственным способом, который гарантировал бы работу, было бы использование as-cast или поддержка:
(List[0] As IBase).DoWhatever;
Но это разочаровывающая стоимость исполнения, поскольку она предназначена для того, чтобы быть включенным в код, добавляя элементы в список, чтобы убедиться, что они относятся к типу IBase - мы должны иметь возможность предположить это (отсюда утверждение поймать if это предположение неверно). Утверждение даже не нужно, за исключением того, что позже пойманы ошибки, если кто-то изменит некоторые типы. Исходный код, из-за которого возникает эта проблема, также довольно критичен по производительности, поэтому затраты на производительность, которые мало что достигают (он по-прежнему только обнаруживает несоответствующие типы во время выполнения, но без возможности компиляции более быстрой сборки), я бы предпочел избежать.
Вторая причина заключается в том, что я хочу иметь возможность сравнивать ссылки для равенства, но это невозможно сделать, если один и тот же объект реализации поддерживается разными ссылками с разными смещениями VMT.
Редактировать 2: В приведенном выше примере расширено редактирование.
Отредактируйте 3: Примечание. Вопрос заключается в том, как я могу сформулировать это утверждение так, чтобы hard-cast был безопасным, если утверждение прошло, а не как избежать жесткого литья. Есть способы сделать упорный шаг по-другому или полностью избежать, но если есть стоимость исполнения во время выполнения, я не могу их использовать. Я хочу, чтобы все затраты на проверку в этом утверждении могли быть скомпилированы позже.
Сказав это, если кто-то может избежать проблемы вообще без каких-либо затрат на производительность и никакой проверки типа, которая была бы большой!