3
votes

In Delphi, if I have a TValue instance reflecting an unknown object, how can I test if this object is an instance of ANY kind of generic TEnumerable<> (or even better, also which specific generic enumerable type it is an instance of, e.g. TList<>)?

NOTE: I already know how to easily check its exact type, i.e. with the .BaseType property of the corresponding TRttiType of the TValue, resulting in for example TList<string>, but what I want to test is rather if it is a TList<> of any sub-item type.

To exemplify how this hypothetical code "IsAnyKindOfGenericEnumerable()" would work, here is some example code:

var
   LContext : TRttiContext;
   obj_1_rtti_value : TValue;
   obj_2_rtti_value : TValue;
   obj_3_rtti_value : TValue;
   obj_1_rtti_type : TRttiType;
   obj_2_rtti_type : TRttiType;
   obj_3_rtti_type : TRttiType;

LContext := TRttiContext.Create();

{
...
obj_1_rtti_value is set to a TValue reflection of a TList<string> object here
obj_2_rtti_value is set to a TValue reflection of a plain TObject object here
obj_3_rtti_value is set to a TValue reflection of a TQueue<integer> object here
...
}

obj_1_rtti_type := LContext.GetType(obj_1_rtti_value.TypeInfo);
obj_2_rtti_type := LContext.GetType(obj_2_rtti_value.TypeInfo);
obj_3_rtti_type := LContext.GetType(obj_3_rtti_value.TypeInfo);

IsAnyKindOfGenericEnumerable(obj_1_rtti_type); //Would return true
IsAnyKindOfGenericEnumerable(obj_2_rtti_type); //Would return false
IsAnyKindOfGenericEnumerable(obj_3_rtti_type); //Would return true

And again, the very best thing would be if I could also detect which kind of TEnumerable<> type it is, like for example:

IsAnyKindOfGenericEnumerable(obj_1_rtti_type); //Will return true + `TList<>`
IsAnyKindOfGenericEnumerable(obj_2_rtti_type); //Will return false
IsAnyKindOfGenericEnumerable(obj_3_rtti_type); //Will return true + `TQueue<>`

I have tried:

if obj_1_rtti_type is TRttiEnumerationType then
begin
   //...
end;

but for some reason this evaluates to false, which I'm completely at loss as to why that is? The expression value_type.BaseType.Name does indeed evaluate to 'TEnumerable<System.string>' in this case, but there really has to be some other way than to manually parse this string in order to accomplish my objective, right?

Finally, the goal must be accomplished solely using the RTTI info, that is, any "cheating" by referring to the real object behind the TValue is not permitted (for reasons outside the scope of this question).

1
Why the downvotes without explanations?QuestionOverflow
Some twerp is seemingly obsessively downvoting all my questions, no matter what they are about. Therefore, in order to avoid this from unfairly affecting my "reputation" (my ability to post new questions etc), I ask you to please upvote this question if it was helpful to you.QuestionOverflow

1 Answers

5
votes

There is no RTTI generated for Generic types themselves (they don't exist at runtime), and each specific instantiation (like TList<string>) is a distinct class type with its own distinct RTTI. You would have to check for each individual type, it is not possible to test for any Generic type. Parsing class names is the only way to detect Generic types.

  1. use TRttiType.Name to get the class name as a string ('TList<System.string>').

  2. parse it to detect the presence of angle brackets ('<>').

  3. extract the substring between the brackets ('System.string')

  4. walk the ancestor tree looking for an ancestor whose TRttiType.Name is 'TEnumerable<...>', where ... is the extracted substring ('TEnumerable<System.string>').

However, this approach fails for class types that derive from TEnumerable<T> but do not have Generics parameters themselves, eg:

type
  TMyClass = class(TEnumerable<string>)
  end;

To account for that, ignore steps 1-3 and jump right to step 4 by itself, ignoring whatever value appears between the brackets, eg:

function IsAnyKindOfGenericEnumerable(AType: TRttiType): Boolean;
begin
  Result := False;
  while AType <> nil do
  begin
    Result := StartsText('TEnumerable<', AType.Name);
    if Result then Exit;
    AType := AType.BaseType;
  end;
end;

As for TRttiEnumerationType, it represents enumerated types (ie: type typeName = (val1, ...,valn);). It has nothing to do with TEnumerable<T>. That is why the is operator is always returning False for you - none of the RTTI types you are testing represent enums.