Conditional behaviour based on concrete type for generic class

后端 未结 4 1787
北恋
北恋 2020-12-06 05:06

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

4条回答
  •  执笔经年
    2020-12-06 05:46

    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 = 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.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.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.Get(idx: T): Byte;
    begin
      Result := fArr[IdxToInt(idx)];
    end;
    
    procedure TEnumArr.Put(idx: T; Val: Byte);
    begin
      fArr[IdxToInt(idx)] := Val;
    end;
    

    Sample of usage:

    type
      TEnum  = (enOne, enTwo, enThree);
    var
      tst: TEnumArr;
    begin
      tst := TEnumArr.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.

提交回复
热议问题