Search code examples
delphigenericsrttidelphi-10.1-berlin

Is there a way to get GUID from a generic constraint type?


In the sample code below, the Run<T>() displays the values of GUID IFoo and IFoo<T> interfaces:

type
  IBar = interface
    ['{992E6597-42F1-40F8-B678-C4A86864B030}']
  end;

  IFoo = interface
    ['{0C589AF8-5727-4EAA-BB41-6D51D70B9D35}']
  end;

  IFoo<T> = interface(IFoo)
    ['{8FF54F6B-0896-4EA3-85F8-66BA70F9D2DA}']
  end;

  TTest = class
  public
    class procedure Run<T: IFoo>;
  end;

class procedure TTest.Run<T>;
var
  LContext: TRttiContext;
  IFoo_T_TypeInfo: PTypeInfo;
  IFooTypeInfo: PTypeInfo;
begin
  IFoo_T_TypeInfo := TypeInfo(T);
  IFooTypeInfo    := LContext.GetType(TypeInfo(T)).BaseType.Handle;

  WriteLn('IFoo<T> GUID: ', GetTypeData(IFoo_T_TypeInfo).GUID.ToString);
  WriteLn('IFoo    GUID: ', GetTypeData(IFooTypeInfo).GUID.ToString);
  WriteLn('IBar    GUID: ', '?');
end;

begin
  TTest.Run<IFoo<IBar>>;
  ReadLn;
end.

Is there a way to get TGUID or PTypeInfo from a generic constraint type, IBar interface in this case?

P.S.: I wouldn't like change the signature of Run<T>() to Run<T, U>() for just get the IBar GUID from U.


Solution

  • Getting typeinfo/RTTI from generic type parameters is a bit tricky but not completely impossible.

    Here is some example code how to do that (I am using the RTTI extensions from the Spring.Reflections unit).

    uses
      Rtti,
      SysUtils,
      Spring.Reflection;
    
    type
      TTest = class
      public
        class procedure Run<T: IFoo>;
      end;
    
    class procedure TTest.Run<T>;
    var
      LType, LType2: TRttiType;
    begin
      LType := TType.GetType(TypeInfo(T));
      if LType.IsInterface then
      begin
        if LType.AsInterface.HasGuid then
          Writeln(LType.Name, ' GUID: ', LType.AsInterface.GUID.ToString);
        LType2 := LType.BaseType;
        while Assigned(LType2) and (LType2.Handle <> TypeInfo(IInterface)) do
        begin
          if LType2.AsInterface.HasGuid then
            Writeln(LType2.Name, ' GUID: ', LType2.AsInterface.GUID.ToString);
          LType2 := LType2.BaseType;
        end;
    
        if LType.IsGenericType then
        begin
          for LType2 in LType.GetGenericArguments do
            if Assigned(LType2) and LType2.IsInterface then
              Writeln(LType2.Name, ' GUID: ', LType2.AsInterface.GUID.ToString);
        end;
      end
    end;
    
    var
      bar: IBar;
    begin
      bar := TBar.Create; // cause RTTI for IBar to be generated to look it up later
      TTest.Run<IFoo<IBar>>;
      ReadLn;
    end.
    

    The check if the type is generic is done via string parsing of the type name. If it contains angle brackets it is a generic type. It then extracts the type names which are always full qualified type names which makes it possible to look them up.

    However there is one gotcha to keep in mind. You can only look those up when the type info for that type was generated in some other context than just the generic type parameter. That is why in that sample I made a simple TBar class that implements IBar and created some instance to prevent the linker to strip that class (and the necessary RTTI). In real code this is less of an issue because you typically have some implementations of that interface. Also for this example to work you need to put the interface into their own unit because lookup by full qualified name does not work for types in the dpr.