Comportamiento condicional basado en tipo concreto para clase genérica.
-
03-07-2019 - |
Pregunta
Debido a que mi pregunta de ayer quizás no fue del todo clara y no obtuve la respuesta Quería, intentaré formularlo de una manera más general:
¿Existe una manera de implementar un comportamiento especial basado en el tipo real de un tipo genérico instanciado, ya sea utilizando sentencias condicionales explícitas o usando algún tipo de especialización? Pseudocódigo:
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;
Solución
Puede recurrir a RTTI utilizando TypeInfo (T) = TypeInfo (cadena)
. Para probar si algo es una clase, puedes usar algo como PTypeInfo (TypeInfo (T)) ^. Kind = tkClass
.
PTypeInfo
y el miembro de enumeración tkClass
se definen en la unidad TypInfo
. Otros consejos
Si a alguien le interesa cómo implementé mi " tamaño de peor caso con tratamiento especial para cadenas "
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;
Para mí, esto hace el trabajo en cuestión. ¡Gracias a todos los colaboradores!
en C #, puedes hacer un typeof (T)
que te permita hacer algo como
(T = String)
o
(T is class)
No he visto tu otra pregunta (no te vinculaste), pero ¿qué estás realmente buscando? En general, hacer algo condicional en el tipo o un código de tipo a través de ifs como lo está haciendo o un interruptor generalmente se transforma mejor para tener una interfaz o función abstracta en algún lugar que se personaliza por contexto.
TypeInfo (T) es la forma correcta. Además, puede usar todas las cosas de la unidad TypInfo como el registro TTypeData para determinar algunas propiedades específicas de un tipo que usa en lugar de genérico. Cuando determine el tipo actual utilizado en lugar de T, puede usar el puntero del puntero para obtener el valor de una variable.
Aquí hay un código de ejemplo que acepta cualquier tipo de enumeración como genérico. Tenga en cuenta que solo funcionará para las enumeraciones habituales (sin valores fijos como
TEnumWontWork = (primero = 1, segundo, tercero)
) y la enumeración no debe declararse como tipo local dentro de un procedimiento. En estos casos, el compilador no genera TypeInfo para las enumeraciones.
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;
Muestra de uso:
type
TEnum = (enOne, enTwo, enThree);
var
tst: TEnumArr<TEnum>;
begin
tst := TEnumArr<TEnum>.Create;
tst[enTwo] := $FF;
Log(tst[enTwo]);
Como currículum, utilicé tres trucos aquí:
1) Obtención de TypeInfo for T con accesorios generales de T
2) Obtención de TypeData para T con accesorios detallados de T
3) Usar la magia del puntero para obtener el valor de los parámetros dados a partir del tipo T.
Espero esta ayuda.