Условное поведение на основе конкретного типа для универсального класса
-
03-07-2019 - |
Вопрос
Поскольку мой вопрос от вчера возможно было не совсем понятно и я не получил желаемого ответа, попробую сформулировать его в более общем виде:
Есть ли способ реализовать особое поведение на основе фактического типа созданного универсального типа, используя явные условные операторы или используя какую-то специализацию?Псевдокод:
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;
Решение
Вы можете вернуться к RTTI, используя TypeInfo(T) = TypeInfo(string)
.Чтобы проверить, является ли что-то классом, вы можете использовать что-то вроде PTypeInfo(TypeInfo(T))^.Kind = tkClass
.
А PTypeInfo
тип и tkClass
член перечисления определяется в TypInfo
единица.
Другие советы
Если кому-то интересно, как я реализовал свой «размер наихудшего случая со специальной обработкой строк»
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;
Для меня это делает работу под рукой.Спасибо всем участникам!
в C# вы можете сделать typeof(T)
который позволит вам сделать что-то вроде
(T = String)
или
(T is class)
Я не видел другого вашего вопроса (вы не дали на него ссылку), но что вы Действительно находясь в поиске?В общем, выполнение чего-то условного по типу или типовому коду с помощью if, как вы делаете, или переключателя, как правило, лучше всего трансформировать в наличие где-то интерфейса или абстрактной функции, которая настраивается в зависимости от контекста.
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.
Надеюсь, это поможет.