Since my question from yesterday was perhaps not completely clear and I did not get the answer I wanted, I will try to formulate it in a more general way:
Is there a
If someone is interested how I did implement my "worst-case size with special treatment for strings"
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;
For me, this does the job at hand. Thanks to all contributors!
TypeInfo(T) is the right way. Moreover you can use all the stuff from TypInfo unit like TTypeData record to determine some specific properties of a type you use instead of generic. When you determine the the current type used instead of T, you may use pointer trick to get a value of a variable.
Here's a sample code that accepts any enumeration type as generic. Note that it will work for usual enumerations only (without fixed values like
TEnumWontWork = (first = 1, second, third)
) and the enum mustn't be declared as local type inside a procedure. In these cases compiler generates no TypeInfo for the 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;
Sample of usage:
type
TEnum = (enOne, enTwo, enThree);
var
tst: TEnumArr<TEnum>;
begin
tst := TEnumArr<TEnum>.Create;
tst[enTwo] := $FF;
Log(tst[enTwo]);
As a resume, I used three tricks here:
1) Getting TypeInfo for T with general props of T
2) Getting TypeData for T with detailed props of T
3) Using pointer magic to get the value of parameters given as of type T.
Hope this help.
in C#, you can do a typeof(T)
which would allow you to do something like
(T = String)
or
(T is class)
I havent seen your other question (you didnt link to it), but what are you really looking for? In general, doing something conditional on type or a typecode via ifs like you are doing or a switch is generally best transformed into having an interface or abstract function somewhere that gets customised by context.
You can fall back to RTTI, by using TypeInfo(T) = TypeInfo(string)
. To test to see if something is a class, you could use something like PTypeInfo(TypeInfo(T))^.Kind = tkClass
.
The PTypeInfo
type and tkClass
enumeration member are defined in the TypInfo
unit.