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

喜夏-厌秋 提交于 2021-01-28 06:42:38

问题


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.


回答1:


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.



来源:https://stackoverflow.com/questions/51368925/is-there-a-way-to-get-guid-from-a-generic-constraint-type

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!