2009-04-30 11 views
13

Desde mi pregunta de yesterday fue tal vez no del todo clara y no he tenido la respuesta que quería, voy a tratar de formular de una manera más general:comportamiento condicional en función del tipo concreto de clase genérica

¿Hay una forma de implementar un comportamiento especial basado en el tipo real de un tipo genérico instanciado ya sea usando declaraciones condicionales explictas 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; 

Respuesta

19

Puede recurrir a RTTI, utilizando TypeInfo(T) = TypeInfo(string). Para probar para ver si algo es una clase, podría usar algo como PTypeInfo(TypeInfo(T))^.Kind = tkClass.

El tipo PTypeInfo y el miembro de enumeración tkClass se definen en la unidad TypInfo.

+1

+1 ¡Muchas gracias! ¡Eso hace exactamente lo que estaba buscando! Incluso si no eres fanático de mi idea original;) – jpfollenius

1

en C#, se puede hacer un typeof(T) lo que permitirá hacer algo así como

(T = String) 

o

(T is class) 

No he visto su otra pregunta (que enlace aún no ha para ello), pero ¿qué estás realmente buscando? En general, hacer algo condicional en tipo o un tipo de letra a través de ifs como lo está haciendo o un cambio generalmente se transforma mejor en tener una interfaz o función abstracta en alguna parte que se personalice por contexto.

+0

Agregué el enlace a la otra pregunta. Básicamente quiero una estimación aproximada para el tamaño en bytes. Quiero tratar cadenas diferentes a otros tipos. – jpfollenius

+0

Quien haya votado negativamente, no dude en comentar sus motivos. Creo que fue una respuesta razonable – jpfollenius

+0

¡Ah, olde SO el sistema de karma a veces sorprende gratamente, gracias! Curiosamente, "fui yo quien elevó tu respuesta a ti mismo" (pero no votó inicialmente la pregunta: P) –

3

Si alguien está interesado cómo me puso en práctica mi "tamaño peor de los casos con un tratamiento especial para las 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 a mano. ¡Gracias a todos los colaboradores!

1

TypeInfo (T) es el camino correcto. Además, puede usar todo el contenido de la unidad TypInfo como el registro TTypeData para determinar algunas propiedades específicas de un tipo que utiliza en lugar de genérico. Cuando determina el tipo actual utilizado en lugar de T, puede usar el truco del puntero para obtener el valor de una variable.

Aquí hay un código de muestra que acepta cualquier tipo de enumeración como genérico. Tenga en cuenta que va a trabajar para enumeraciones habituales solamente (sin valores fijos como

TEnumWontWork = (primera = 1, segundo, tercero)

) y la enumeración no debe ser declarado como tipo de locales 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; 

Ejemplo de uso:

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

Como resumen, he usado tres trucos aquí:

1) Cómo TypeInfo para T con los apoyos generales de T

2) Como llegar TypeData para T con detalles de T

3) Uso de magia de puntero para obtener el valor de parame ters dados a partir del tipo T.

Espero que esta ayuda.

Cuestiones relacionadas