Условное поведение на основе конкретного типа для универсального класса - PullRequest
15 голосов
/ 30 апреля 2009

Так как мой вопрос от вчера , возможно, был не совсем ясен, и я не получил ответ, который хотел, я постараюсь сформулировать его более общим образом:

Есть ли способ реализовать специальное поведение, основанное на фактическом типе инстанцированного универсального типа, либо используя условные операторы explict, либо используя какую-то специализацию? Псевдокод:

TGenericType <T> = class
  function Func : Integer;
end;
...
function TGenericType <T>.Func : Integer;
begin
  if (T = String) then Exit (0);
  if (T is class) then Exit (1);
end;
...
function TGenericType <T : class>.Func : Integer;
begin
Result := 1;
end;
function TGenericType <String>.Func : Integer;
begin
Result := 0;
end;

Ответы [ 4 ]

22 голосов
/ 30 апреля 2009

Вы можете вернуться к RTTI, используя TypeInfo(T) = TypeInfo(string). Чтобы проверить, является ли что-то классом, вы можете использовать что-то вроде PTypeInfo(TypeInfo(T))^.Kind = tkClass.

Тип PTypeInfo и член перечисления tkClass определены в единице TypInfo.

3 голосов
/ 30 апреля 2009

Если кому-то интересно, как я реализовал свой «худший размер со специальной обработкой для строк» ​​

class function RTTIUtils.GetDeepSize <T> (Variable : T) : Integer;
var
  StringLength          : Integer;
  Ptr                   : PInteger;
begin
if (TypeInfo (T) = TypeInfo (String)) then
  begin
  Ptr := @Variable;
  Ptr := PInteger (Ptr^);
  Dec (Ptr);
  StringLength := Ptr^;
  Result := StringLength * SizeOf (Char) + 12;
  end
else
  Result := 0;
end;

Для меня это работа под рукой. Спасибо всем участникам!

1 голос
/ 30 января 2013

TypeInfo (T) - правильный путь. Более того, вы можете использовать весь материал из модуля TypInfo, например запись TTypeData, чтобы определить некоторые специфические свойства типа, который вы используете вместо универсального. Когда вы определяете текущий тип, используемый вместо T, вы можете использовать указатель, чтобы получить значение переменной.

Вот пример кода, который принимает любой тип перечисления как универсальный. Обратите внимание, что он будет работать только для обычных перечислений (без фиксированных значений, таких как

TEnumWontWork = (первый = 1, второй, третий)

) и перечисление не должно быть объявлено как локальный тип внутри процедуры. В этих случаях компилятор не генерирует TypeInfo для перечислений.

type
  // Sample generic class that accepts any enumeration type as T
  TEnumArr<T> = class
  strict private
    fArr: array of Byte;
    fIdxType: TOrdType;
    function IdxToInt(idx: T): Int64;
    procedure Put(idx: T; Val: Byte);
    function Get(idx: T): Byte;
  public
    constructor Create;
    property Items[Index: T]: Byte read Get write Put; default;
  end;

constructor TEnumArr<T>.Create;
var
  pti: PTypeInfo;
  ptd: PTypeData;
begin
  pti := TypeInfo(T);
  if pti = nil then
    Error('no type info');
  // Perform run-time type check
  if pti^.Kind <> tkEnumeration then
    Error('not an enum');
  // Reach for TTypeData record that goes right after TTypeInfo record
  // Note that SizeOf(pti.Name) won't work here
  ptd := PTypeData(PByte(pti) + SizeOf(pti.Kind) + (Length(pti.Name)+1)*SizeOf(AnsiChar));
  // Init internal array with the max value of enumeration
  SetLength(fArr, ptd.MaxValue);
  // Save ordinal type of the enum
  fIdxType := ptd.OrdType;
end;

// Converts index given as enumeration item to integer.
// We can't just typecast here like Int64(idx) because of compiler restrictions so
//  use pointer tricks. We also check for the ordinal type of idx as it may vary
//  depending on compiler options and number of items in enumeration.
function TEnumArr<T>.IdxToInt(idx: T): Int64;
var
  p: Pointer;
begin
  p := @idx;

  case fIdxType of
    otSByte: Result := PShortInt(p)^;
    otUByte: Result := PByte(p)^;
    otSWord: Result := PSmallInt(p)^;
    otUWord: Result := PWord(p)^;
    otSLong: Result := PLongInt(p)^;
    otULong: Result := PLongWord(p)^;
  end;
end;

function TEnumArr<T>.Get(idx: T): Byte;
begin
  Result := fArr[IdxToInt(idx)];
end;

procedure TEnumArr<T>.Put(idx: T; Val: Byte);
begin
  fArr[IdxToInt(idx)] := Val;
end;

Пример использования:

type
  TEnum  = (enOne, enTwo, enThree);
var
  tst: TEnumArr<TEnum>;
begin
  tst := TEnumArr<TEnum>.Create;
  tst[enTwo] := $FF;
  Log(tst[enTwo]);

В качестве резюме я использовал здесь три уловки:

1) Получение TypeInfo для T с общими реквизитами T

2) Получение TypeData для T с подробным описанием T

3) Использование указателя для получения значения параметров, заданных как тип T.

Надеюсь, эта помощь.

1 голос
/ 30 апреля 2009

в C # вы можете сделать typeof(T), что позволит вам сделать что-то вроде

(T = String)

или

(T is class)

Я не видел ваш другой вопрос (вы не указали на него), но что вы действительно ищете? В общем, выполнение чего-то условного в отношении типа или Typecode с помощью ifs, как вы делаете, или переключателя, как правило, лучше всего преобразовывать в наличие интерфейса или абстрактной функции где-то, что настраивается в контексте.

...