简体   繁体   English

基于泛型类的具体类型的条件行为

[英]Conditional behaviour based on concrete type for generic class

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 way to implement special behaviour based on the actual type of an instantiated generic type either using explict conditional statements or using some kind of specialization? 有没有办法基于实例化的泛型类型的实际类型实现特殊行为,使用explict条件语句或使用某种特化? 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;

You can fall back to RTTI, by using TypeInfo(T) = TypeInfo(string) . 您可以使用TypeInfo(T) = TypeInfo(string)回退到RTTI。 To test to see if something is a class, you could use something like PTypeInfo(TypeInfo(T))^.Kind = tkClass . 要测试某些东西是否为类,可以使用类似PTypeInfo(TypeInfo(T))^.Kind = tkClass

The PTypeInfo type and tkClass enumeration member are defined in the TypInfo unit. PTypeInfo类型和tkClass枚举成员在TypInfo单元中定义。

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. TypeInfo(T)是正确的方法。 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. 此外,您可以使用TypInfo单元中的所有内容(如TTypeData记录)来确定您使用的类型的某些特定属性,而不是泛型。 When you determine the the current type used instead of T, you may use pointer trick to get a value of a variable. 当您确定使用的当前类型而不是T时,您可以使用指针技巧来获取变量的值。

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) TEnumWontWork =(第一= 1,第二,第三)

) and the enum mustn't be declared as local type inside a procedure. )并且枚举不得在过程中声明为本地类型。 In these cases compiler generates no TypeInfo for the enums. 在这些情况下,编译器不会为枚举生成TypeInfo。

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 1)使用T的常规道具获取T的TypeInfo

2) Getting TypeData for T with detailed props of T 2)使用T的详细道具获取T的TypeData

3) Using pointer magic to get the value of parameters given as of type T. 3)使用指针魔法获取T类型给出的参数值。

Hope this help. 希望这有帮助。

in C#, you can do a typeof(T) which would allow you to do something like 在C#中,你可以做一个typeof(T) ,它可以让你做类似的事情

(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. 一般来说,通过类似于你正在做的事情或者切换来做类型或类型代码的条件通常最好转换为在某个地方通过上下文定制的接口或抽象函数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM