For a piece of code that needs the type "family" of a generic type, I try to use the TypeInfo
to retrieve the required information.
class function GetTypeKind<T>:TTypeKind;
For most types I can figure this out. But the anonymous method type behaves unexpected.
I have an anonymous method type defined as:
TMethodProc = reference to procedure;
And I try to get the type information:
MyKind := GetTypeKind<TMethodProc>;
class function GetTypeKind<T>:TTypeKind;
var
TI: PTypeInfo;
begin
TI := TypeInfo(T);
...
end;
I know there is some compiler magic behind the anonymous methods. But I get the following result:
TI.TypeData.IntfParent == IInterface
TI.TypeData.IntfFlags == [(out of bounds)6]
The flags have an unexpected value, TIntfFlag
has three values so a 6 is unexpected. The GUID is also not a guid. It has a repeating set of the same 8 bytes, mostly 00. For example (0, 225, 48, 180, 0, 0, 0, 0, 0, 225, 48, 180, 0, 0, 0, 0)
Are anonymous methods excluded from TypeInfo
or can it be useful with some tweaks.
Also, is the (strange) 6 an undocumented feature, or can it be any value?
There is nothing really unusual about this.
An anonymous method is implemented as a compiler-generated interface that has an Invoke()
method matching the same signature as the anonymous method. That is why the TTypeKind
is tkInterface
and the IntfParent
is IInterface
.
Behind the interface is a compiler-generated implementation class that contains the captured variables, and the body of the anonymous method within its Invoke()
implementation.
How are anonymous methods implemented under the hood?
The IntfFlags
is a TIntfFlagsBase
, which is a Set
of TIntfFlag
enum values:
TIntfFlag = (ifHasGuid, ifDispInterface, ifDispatch);
ifHasGuid
Interface has a GUID (Globally Unique Identifier).ifDispInterface
Is a dispatch interface.ifDispatch
Can be dispatched.
A Set
is a bitmask of values. Each enum value is represented by a specific bit in the mask. Within TIntfFlagsBase
, ifHasGuid
is bit 0, ifDispInterface
is bit 1, and ifDispatch
is bit 2. So, a numeric value of 6 (110b
) would be the ifDispInterface
and ifDispatch
flags enabled, but not the ifHasGuid
flag. As such, the IntfGuid
has no meaningful value, but still takes up space in the TTypeData
for alignment purposes.
Update: I tested with XE2, and sure enough, I see IntfFlags
is set to ordinal 64 (TIntfFlag(6)
, like you see) instead of ordinal 6, as expected. The only difference between what I see and what you see is that I see the Guid
is completely empty (all zeros).
Update: apparently, there are indeed additional flags present for interfaces that have method info enabled (the {$M+}
directive), or that represent anonymous method types, which are not represented in the TIntfFlag
enum. I have filed a bug report for that:
RSP-24631: System.TypInfo.TIntfFlag enum is missing flags
In this case, TIntfFlag(6)
is the flag for anonymous methods.
From Undocumented "Interface flag" for IInvokable?:
It seems indeed as if the TIntfFlag enum was never extended since Delphi6 (I think that was when interface RTTI was introduced) - I can confirm that at least since XE an interface type with $M+ gets a fourth flag (lets call it ifHasMethodInfo) set.
If the type is an anonymous method type ... then there is a 7th enum value in the set. The situations where bit 5 and 6 are set are unknown to me.
...
I can confirm my findings with this code:
uses SysUtils, Rtti; type TIntfFlagEx = (ifHasGuid, ifDispInterface, ifDispatch, ifMethodInfo, ifUnknown, ifUnknown2, ifAnonymousMethod); TIntfFlagsEx = set of TIntfFlagEx; {$M+} IFoo = interface ['{35CFB4E2-4A13-48E9-8026-C1558001F4B7}'] procedure Main; end; {$M-} {$M+} IBar = interface(TProc) ['{AB2FEC1A-339F-4E58-B3DB-EC7B734F461B}'] end; {$M-} {$M+} TMyProc = reference to procedure; {$M-} procedure PrintIntf(typeInfo: Pointer); var context: TRttiContext; rttiInterface: TRttiInterfaceType; flags: TIntfFlagsEx; begin rttiInterface := context.GetType(typeInfo) as TRttiInterfaceType; flags := TIntfFlagsEx(rttiInterface.IntfFlags); Writeln(rttiInterface.Name, ' ', TValue.From(flags).ToString); end; begin PrintIntf(TypeInfo(IInterface)); PrintIntf(TypeInfo(IInvokable)); PrintIntf(TypeInfo(IFoo)); PrintIntf(TypeInfo(TProc)); PrintIntf(TypeInfo(TFunc<Integer>)); PrintIntf(TypeInfo(TMyProc)); PrintIntf(TypeInfo(IBar)); Readln; end.
prints this:
IInterface [ifHasGuid] IInvokable [ifMethodInfo] IFoo [ifHasGuid,ifMethodInfo] TProc [ifAnonymousMethod] TFunc [ifAnonymousMethod] TMyProc [ifMethodInfo,ifAnonymousMethod] IBar [ifHasGuid,ifMethodInfo,ifAnonymousMethod]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With