Comportement conditionnel basé sur un type concret pour une classe générique
-
03-07-2019 - |
Question
Depuis ma question de hier n'était peut-être pas tout à fait claire et je n'ai pas eu la réponse. Je voulais, je vais essayer de le formuler de manière plus générale:
Existe-t-il un moyen d'implémenter un comportement spécial basé sur le type réel d'un type générique instancié, en utilisant des instructions conditionnelles explicites ou en utilisant une sorte de spécialisation? Pseudocode:
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;
La solution
Vous pouvez revenir à RTTI en utilisant TypeInfo (T) = TypeInfo (chaîne)
. Pour vérifier si quelque chose est une classe, vous pouvez utiliser quelque chose comme PTypeInfo (TypeInfo (T)) ^. Kind = tkClass
.
Le type PTypeInfo
et le membre d'énumération tkClass
sont définis dans l'unité TypInfo
.
Autres conseils
Si quelqu'un s'intéresse à la façon dont j'ai implémenté ma "taille minimale" avec un traitement spécial pour les chaînes "
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;
Pour moi, cela fait le travail à portée de main. Merci à tous les contributeurs!
en C #, vous pouvez faire un typeof (T)
qui vous permettrait de faire quelque chose comme
(T = String)
ou
(T is class)
Je n’ai pas vu votre autre question (vous n’avez pas créé de lien), mais que cherchez-vous vraiment ? En général, il est préférable de transformer une chose conditionnée par un type ou un code de code via ifs ou un commutateur en une interface ou une fonction abstraite personnalisable en fonction du contexte.
TypeInfo (T) est la bonne façon. De plus, vous pouvez utiliser tous les éléments de l'unité TypInfo tels que l'enregistrement TTypeData pour déterminer certaines propriétés spécifiques d'un type que vous utilisez plutôt que générique. Lorsque vous déterminez le type actuel utilisé au lieu de T, vous pouvez utiliser l'astuce du pointeur pour obtenir la valeur d'une variable.
Voici un exemple de code qui accepte tout type d’énumération en tant que générique. Notez que cela fonctionnera uniquement pour les énumérations habituelles (sans valeurs fixes telles que
TEnumWontWork = (premier = 1, deuxième, troisième)
) et l'énumération ne doit pas être déclarée comme type local dans une procédure. Dans ces cas, le compilateur ne génère pas TypeInfo pour les énumérations.
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;
Exemple d'utilisation:
type
TEnum = (enOne, enTwo, enThree);
var
tst: TEnumArr<TEnum>;
begin
tst := TEnumArr<TEnum>.Create;
tst[enTwo] := $FF;
Log(tst[enTwo]);
En résumé, j’ai utilisé trois astuces ici:
1) Obtention de TypeInfo pour T avec les accessoires généraux de T
2) Obtention de TypeData for T avec les accessoires détaillés de T
3) Utilisation de la magie de pointeur pour obtenir la valeur de paramètres donnée en tant que type T.
J'espère que cela vous aidera.