Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[35.173.48.18] |
|
Сообщ.
#1
,
|
|
|
Generics: определение текущего типа в run-time
Иногда требуется определить, какой именно тип использован вместо generic-а, и на основе этой информации сделать некое ветвление. Многие гуру всячески отвращают от такой практики, однако бывают случаи, когда это необходимо. Ниже представлен один из них, когда в качестве generic-а используется перечисление. Штатными каноничными средствами задача решается только перекрытием методов под каждый конкретный тип, однако есть и способы обойти это ограничение: 1) var pti: PTypeInfo; pti := TypeInfo(T) позволит получить текущий вид типа Т (строка, объект, целое, перечисление...) и его название. Следует заметить, что TypeInfo генерируется не для всех типов, например, для перечислений, объявленных локально в подпрограмме, или с фиксированным числовым значением (TEnumWontWork = (first = 1, second, third)) в pti будет nil. 2) Можно также получить ссылку на структуру TypeData, которая прячется в хвосте TypeInfo и содержит специфичные свойства типа. var ptd: PTypeData; ptd := PTypeData(PByte(pti) + SizeOf(pti.Kind) + (Length(pti.Name)+1)*SizeOf(AnsiChar)); 3) Магия указателей для получения значения переменной. procedure TGenericClass<T>.Method(v: T); var p: Pointer; p := @v; // дальше можно разыменовывать p на основе TypeInfo(T) как заблагорассудится Разумеется, надо не забывать про всяческие проверки и быть осторожным, но эти способы позволят сделать с generic-ами чуть больше, чем предусмотрено штатными средствами. В качестве примера - класс, принимающий любое перечисление в качестве T 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; if (Result < Low(fArr)) or (Result > High(fArr)) then Error('Wrong index!'); 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; Пример использования type TEnum = (enOne, enTwo, enThree); var tst: TEnumArr<TEnum>; begin tst := TEnumArr<TEnum>.Create; tst[enTwo] := $FF; Log(tst[enTwo]); |
Сообщ.
#2
,
|
|
|
Наваял целый класс в качестве обертки для GetEnumName/GetEnumValue
Функции для преобразования enum <=> Integer/string, каждая перегружена (может принимать предварительно закэшированный OrdType). uses TypInfo; TEnum<T> = class class function GetOrdType: TOrdType; // T => Int class function Int(item: T): Integer; overload; class function Int(OrdType: TOrdType; item: T): Integer; overload; // Int => T class function Val(item: Integer): T; overload; class function Val(OrdType: TOrdType; item: Integer): T; overload; // T => Str class function Str(item: T): string; overload; class function Str(OrdType: TOrdType; item: T): string; overload; // Str => T class function Val(item: string): T; overload; class function Val(OrdType: TOrdType; item: string): T; overload; end; const S_EnumNoTypeInfo = 'No type info for T (local enum, enum with fixed values or something else)'; S_EnumNotAnEnum = 'Type "%s" is not an enum'; class function TEnum<T>.GetOrdType: TOrdType; var pti: PTypeInfo; ptd: PTypeData; begin pti := TypeInfo(T); // type info check if pti = nil then raise Exception.Create(S_EnumNoTypeInfo); // run-time type check if pti^.Kind <> tkEnumeration then raise Exception.Create(Format(S_EnumNotAnEnum, [pti^.Name])); // 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)); // Save ordinal type of the enum Result := ptd.OrdType; end; class function TEnum<T>.Val(OrdType: TOrdType; item: Integer): T; var p: Pointer; begin p := @Result; case OrdType of otSByte: PShortInt(p)^ := ShortInt(item); otUByte: PByte(p)^ := Byte(item); otSWord: PSmallInt(p)^ := SmallInt(item); otUWord: PWord(p)^ := Word(item); otSLong: PLongInt(p)^ := LongInt(item); otULong: PLongWord(p)^ := LongWord(item); end; end; class function TEnum<T>.Val(item: Integer): T; begin Result := Val(GetOrdType, item); end; class function TEnum<T>.Int(OrdType: TOrdType; item: T): Integer; var p: Pointer; begin p := @item; case GetOrdType 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; class function TEnum<T>.Int(item: T): Integer; begin Result := Int(GetOrdType, item); end; class function TEnum<T>.Str(OrdType: TOrdType; item: T): string; begin Result := GetEnumName(TypeInfo(T), Int(OrdType, item)); end; class function TEnum<T>.Str(item: T): string; begin Result := GetEnumName(TypeInfo(T), Int(item)); end; class function TEnum<T>.Val(OrdType: TOrdType; item: string): T; begin Result := Val(OrdType, GetEnumValue(TypeInfo(T), item)); end; class function TEnum<T>.Val(item: string): T; begin Result := Val(GetEnumValue(TypeInfo(T), item)); end; Примеры: Log(TEnum<TFormState>.Str(Form1.FormState)); if Form1.Position = TEnum<TPosition>.Val('poDesigned') then Log('yes'); К большому сожалению, с множествами фокус не пройдет. И не спрашивайте, чем это лучше GetEnumName(TypeInfo(TEnumType), Int(item)) |