comportamento condicional com base no tipo concreto de classe genérica
-
03-07-2019 - |
Pergunta
Desde a minha pergunta de talvez não fosse totalmente claro e eu não obter a resposta Eu queria, eu vou tentar formulá-la de uma forma mais geral:
Existe uma maneira de implementar um comportamento especial com base no tipo real de um tipo genérico instanciado ou usando instruções condicionais explict ou usando algum tipo de especialização? 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;
Solução
Você pode cair de volta para RTTI, usando TypeInfo(T) = TypeInfo(string)
. Para testar para ver se algo é uma classe, você poderia usar algo como PTypeInfo(TypeInfo(T))^.Kind = tkClass
.
O tipo PTypeInfo
e membro tkClass
enumeração são definidas na unidade TypInfo
.
Outras dicas
Se alguém estiver interessado como eu fiz implementar o meu "tamanho pior caso com tratamento especial para cordas"
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 mim, isso faz o trabalho na mão. Graças a todos os contribuintes!
em C #, você pode fazer um typeof(T)
que lhe permitiria fazer algo como
(T = String)
ou
(T is class)
Eu não vi sua outra pergunta (você não fez link para ele), mas o que você realmente procurando? Em geral, fazendo condicional algo do tipo ou um typecode via ifs como você está fazendo ou um interruptor é geralmente melhor transformado em ter um em algum lugar da interface ou função abstrata que fica personalizado pelo contexto.
TypeInfo (T) é o caminho certo. Além disso, você pode usar todo o material da unidade TypInfo como registro TTypeData para determinar algumas propriedades específicas de um tipo que você usar em vez de genérico. Quando você determinar o tipo de corrente utilizado em vez de T, você pode usar truque ponteiro para obter um valor de uma variável.
Aqui está um código de exemplo que aceita qualquer tipo de enumeração como genérico. Note-se que ele irá trabalhar para enumerações habituais somente (sem valores fixos, como
= TEnumWontWork (primeira = 1, segundo, terceiro)
) ea enumeração não deve ser declarado como tipo local dentro de um procedimento. Nestes casos compilador não gera TypeInfo para os enums.
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;
Amostra de uso:
type
TEnum = (enOne, enTwo, enThree);
var
tst: TEnumArr<TEnum>;
begin
tst := TEnumArr<TEnum>.Create;
tst[enTwo] := $FF;
Log(tst[enTwo]);
Como um currículo, eu usei três truques aqui:
1) Obtendo TypeInfo para T com adereços gerais de T
2) Obtendo TypeData para T com adereços detalhados de T
3) Usando magia ponteiro para obter o valor de parâmetros de dados como do tipo T.
Espero que isso ajuda.