Есть несколько способов проверить, реализует ли объект данный интерфейс или нет - но на разных платформах есть небольшие различия.
Преобразование as работает (практически) одинаково в Win32 и .NET.
var MyObject: TMyObject; MyInterface: IMyInterface; begin // ... MyInterface := MyObject as IMyInterface; // может возбудить EInvalidCast MyInterface.Foo; end;Отличия платформ в том, что на Win32 объявленный тип объекта должен реализовывать интерфейс
IInterface
(или IUnknown
), а в .NET подойдёт любой тип объекта. Если преобразование неудачно - возбуждается исключение. Иногда это не то, что вам нужно. На .NET вы можете привести тип от любого объекта к любому интерфейсу, используя синтаксис жёсткого приведения - и это будет работать. Если преобразование будет неудачным, то просто вернётся nil
:
var MyObject: TMyObject; MyInterface: IMyInterface; begin ... MyInterface := IMyInterface(MyObject); // специфично для .NET if Assigned(MyInterface) then MyInterface.Foo; end;В настоящий момент компилятор Win32 не поддерживает такое преобразование в общем случае. Он поддерживает его только в особом случае, когда преобразование может быть проверено на этапе компиляции. Это может быть, когда вы объявляете объект типа, который поддерживает данный интерфейс. Если это не так, то компилятор выдаст вам такую ошибку:
[Error]: Incompatible types: 'IMyInterface' and 'TInterfacedObject'Чтобы проверить, поддерживает ли объект интерфейс без возбуждения исключений, вы должны сначала преобразовать его к
IInterface
, а затем вызвать метод QueryInterface
. Эта возможность существует только в Win32:
Intf := IInterface(MyObject); if Intf.QueryInterface(IMyInterface, MyInteface) = 0 then MyInterface.Foo;Функция
Supports
в модуле SysUtils
даёт вам более удобный (и кросс-платформенный) способ для такой проверки:
if Supports(MyObject,IMyInterface, MyInteface) then MyInterface.Foo;
Supports
также доступна в .NET, но её реализация делает её намного медленнее, чем использование жёсткого преобразования, специфичного для .NET. Это потому что .NET-вариант Supports
реализован в терминах новой конструкции 'type of interface':
type TInterfaceRef = type of interface; ... function Supports(const Instance: TObject; const IID: TInterfaceRef; out Intf): Boolean; begin Result := Instance is IID; if Result then Intf := Instance as IID; end;Конструкция 'type of interface', вообще-то, довольно изящна. Она позволяет вам передавать любой интерфейсный тип параметром в метод. Это используется вместо передачи
TGUID
как идентификатора или описателя данного интерфейса в Win32. Функция Supports
компилируется в такой IL-код:
.method public hidebysig static bool Supports(object Instance, [mscorlib]System.RuntimeTypeHandle IID, object& Intf) cil managed { // Code Size: 50 byte(s) .maxstack 3 .locals ( bool flag1, [mscorlib]System.Type type1) L_0000: ldarg.1 L_0001: call [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle([mscorlib]System.RuntimeTypeHandle) L_0006: ldarg.0 L_0007: callvirt instance bool [mscorlib]System.Type::IsInstanceOfType(object) L_000c: stloc.0 L_000d: ldloc.0 L_000e: brfalse.s L_0030 L_0010: ldarg.2 L_0011: ldarg.1 L_0012: call [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle([mscorlib]System.RuntimeTypeHandle) L_0017: stloc.1 L_0018: ldloc.1 L_0019: ldarg.0 L_001a: callvirt instance bool [mscorlib]System.Type::IsInstanceOfType(object) L_001f: brtrue.s L_0029 L_0021: ldarg.0 L_0022: ldloc.1 L_0023: call object Borland.Delphi.Units.System::@CreateCastException(object, [mscorlib]System.Type) L_0028: throw L_0029: ldarg.0 L_002a: castclass object L_002f: stind.ref L_0030: ldloc.0 L_0031: ret }Довольно много кода!
С небольшой помощью
Reflector
-а, я обнаружил, что этот код примерно соответствует такому Delphi-коду:
function Supports(Instance: TObject; IID: RuntimeTypeHandle; out Intf: TObject): boolean; var InterfaceType: System.Type; begin Result := System.Type.GetTypeFromHandle(IID).IsInstanceOfType(Instance); if Result then begin InterfaceType := System.Type.GetTypeFromHandle(IID); if (not InterfaceType.IsInstanceOfType(Instance)) then raise EInvalidCast.Create(SInvalidCast); Intf := Instance; end; end;Заметьте, что этот код немного избыточен. Наличие обеих конструкций is и as заставляет компилятор вызывать
GetTypeFromHandle
и IsInstanceOfType
дважды. Исключение в этом методе никогда не вызывается. Вызов Supports
генерирует такой IL-код:
ldloc.1 ldtoken Obj2IntfCastNET.IMyInterface call [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle([mscorlib]System.RuntimeTypeHandle) pop ldtoken Obj2IntfCastNET.IMyInterface ldloca.s obj2 call bool Borland.Vcl.Units.SysUtils::Supports(object, [mscorlib]System.RuntimeTypeHandle, object&) stloc.0Сравните это с жёстким преобразованием объект-в-интерфейс, который даёт вам этот простой и эффективный IL-код:
ldloc.s obj2 isinst Obj2IntfCastNET.IMyInterface stloc.2Угадаете, что работает быстрее?
Так что вы можете использовать кросс-платформенную функцию
Supports
(которая достаточно эффективна в Win32, но относительно медленная в .NET), чтобы сделать свой код одинаковым на всех платформах. Или же вы можете писать код для каждой платформы отдельно, и тогда вы можете воспользоваться эффективным на .NET жёстким приведением типа. В большинстве случаев, эти накладные расходы для .NET-варианта Supports
не имеют значения, но для каких-то критичных к времени выполнения кусков вы можете захотеть выбрать самое эффективное решение для этой платформы, например:
{$IFDEF WIN32} if Supports(MyObject,IMyInterface, MyInterface) then {$ENDIF} {$IFDEF CLR} MyInterface := IMyInterface(MyObject); if Assigned(MyInterface) then {$ENDIF} MyInterface.Foo;Я надеюсь, что в будущих версиях компилятора Win32 можно будет делать жёсткое приведение типов от объекта к интерфейсу так же, как сейчас это происходит на .NET. Преобразование должно возвращать
nil
, если объект не поддерживает интерфейс (да, это предложение было отправлено Borland-у). В .NET вы также можете использовать оператор 'is', чтобы проверить, реализует ли объект интерфейс или нет. Это не поддерживается в Win32.
if MyObject is IMyInterface then Writeln('Yup');
Комментариев нет:
Отправить комментарий
Можно использовать некоторые HTML-теги, например:
<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>
Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.
Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.
Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.
Примечание. Отправлять комментарии могут только участники этого блога.