Как я могу проверить, отражает ли неизвестное Delphi RTTI TValue объект, который является ЛЮБЫМ типом универсального TList‹› (или, по крайней мере, TEnumerable‹›)?

В Delphi, если у меня есть экземпляр TValue, отражающий неизвестный объект, как я могу проверить, является ли этот объект экземпляром ЛЮБОГО типа универсального TEnumerable<> (или, что еще лучше, также какого конкретного универсального перечисляемого типа) экземпляр, например TList<>)?

ПРИМЕЧАНИЕ: я уже знаю, как легко проверить его точный тип, т. е. с помощью свойства .BaseType соответствующего TRttiType из TValue, в результате чего, например, будет TList<string>, но я хочу проверить скорее если это TList<> подэлемента любого типа.

Чтобы проиллюстрировать, как этот гипотетический код "IsAnyKindOfGenericEnumerable()" будет работать, вот пример кода:

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

И опять же, было бы лучше всего, если бы я мог определить какой тип TEnumerable<>, например:

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<>`

Я пытался:

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

но по какой-то причине это оценивается как false, и я совершенно не понимаю, почему это так? Выражение value_type.BaseType.Name действительно оценивается как 'TEnumerable<System.string>' в этом случае, но действительно должен быть какой-то другой способ, кроме ручного разбора этой строки, для достижения моей цели, верно?

Наконец, цель должна быть достигнута исключительно с использованием информации RTTI, то есть любой «обман» путем обращения к реальному объекту за TValue не допускается (по причинам, выходящим за рамки этого вопроса).


person QuestionOverflow    schedule 08.03.2015    source источник
comment
Почему минусуют без объяснений?   -  person QuestionOverflow    schedule 09.03.2015
comment
Какой-то придурок, похоже, одержимо отрицает все мои вопросы, независимо от того, о чем они. Поэтому, чтобы избежать несправедливого влияния на мою репутацию (моя способность публиковать новые вопросы и т. д.), я прошу вас проголосовать за этот вопрос, если он был вам полезен.   -  person QuestionOverflow    schedule 20.03.2015


Ответы (1)


Для самих универсальных типов не создается RTTI (они не существуют во время выполнения), и каждый конкретный экземпляр (например, TList<string>) представляет собой отдельный тип класса со своим собственным отличным RTTI. Вам придется проверять каждый отдельный тип, невозможно проверить любой общий тип. Анализ имен классов — единственный способ обнаружить универсальные типы.

  1. используйте TRttiType.Name, чтобы получить имя класса в виде строки ('TList<System.string>').

  2. проанализируйте его, чтобы обнаружить наличие угловых скобок ('<>').

  3. извлечь подстроку между скобками ('System.string')

  4. пройтись по дереву предков в поисках предка, у которого TRttiType.Name равно 'TEnumerable<...>', где ... — извлеченная подстрока ('TEnumerable<System.string>').

Однако этот подход не работает для типов классов, производных от TEnumerable<T>, но не имеющих самих параметров Generics, например:

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

Чтобы учесть это, пропустите шаги 1-3 и сразу перейдите к шагу 4, игнорируя любое значение, указанное в скобках, например:

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;

Что касается TRttiEnumerationType, то он представляет собой нумерованные типы. (т.е.: type typeName = (val1, ...,valn);). Это не имеет ничего общего с TEnumerable<T>. Вот почему оператор is всегда возвращает вам False — ни один из тестируемых вами типов RTTI не представляет перечисления.

person Remy Lebeau    schedule 08.03.2015
comment
Спасибо за ответ (принят), но знаете ли вы (или кто-либо другой), почему за этот вопрос дважды проголосовали против, без каких-либо объяснений в комментариях? Я думаю, что это совершенно правильный вопрос, на который многие другие люди, вероятно, также хотели бы получить ответ? - person QuestionOverflow; 09.03.2015