На главную
ПРАВИЛА FAQ Помощь Участники Календарь Избранное DigiMania RSS
msm.ru
! Друзья, соблюдайте, пожалуйста, правила форума и данного раздела:
Данный раздел не предназначен для вопросов и обсуждений, он содержит FAQ-заготовки для разных языков программирования. Любой желающий может разместить здесь свою статью. Вопросы же задавайте в тематических разделах!
• Если ваша статья может быть перенесена в FAQ соответствующего раздела, при условии, что она будет оформлена в соответствии с Требованиями к оформлению статей.
• Чтобы остальным было проще понять, указывайте в описании темы (подзаголовке) название языка в [квадратных скобках]!
Модераторы: Модераторы
  
> Generics: определение текущего типа в run-time, [Delphi] И получение значения generic-переменных
    Generics: определение текущего типа в run-time

    Иногда требуется определить, какой именно тип использован вместо generic-а, и на основе этой информации сделать некое ветвление. Многие гуру всячески отвращают от такой практики, однако бывают случаи, когда это необходимо. Ниже представлен один из них, когда в качестве generic-а используется перечисление. Штатными каноничными средствами задача решается только перекрытием методов под каждый конкретный тип, однако есть и способы обойти это ограничение:
    1)
    ExpandedWrap disabled
      var
        pti: PTypeInfo;
       
      pti := TypeInfo(T)

    позволит получить текущий вид типа Т (строка, объект, целое, перечисление...) и его название. Следует заметить, что TypeInfo генерируется не для всех типов, например, для перечислений, объявленных локально в подпрограмме, или с фиксированным числовым значением (TEnumWontWork = (first = 1, second, third)) в pti будет nil.

    2) Можно также получить ссылку на структуру TypeData, которая прячется в хвосте TypeInfo и содержит специфичные свойства типа.
    ExpandedWrap disabled
      var
        ptd: PTypeData;
       
        ptd := PTypeData(PByte(pti) + SizeOf(pti.Kind) + (Length(pti.Name)+1)*SizeOf(AnsiChar));


    3) Магия указателей для получения значения переменной.
    ExpandedWrap disabled
      procedure TGenericClass<T>.Method(v: T);
      var
        p: Pointer;
       
        p := @v;
        // дальше можно разыменовывать p на основе TypeInfo(T) как заблагорассудится


    Разумеется, надо не забывать про всяческие проверки и быть осторожным, но эти способы позволят сделать с generic-ами чуть больше, чем предусмотрено штатными средствами.

    В качестве примера - класс, принимающий любое перечисление в качестве T

    ExpandedWrap disabled
      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;


    Пример использования
    ExpandedWrap disabled
      type
        TEnum  = (enOne, enTwo, enThree);
      var
        tst: TEnumArr<TEnum>;
      begin
        tst := TEnumArr<TEnum>.Create;
        tst[enTwo] := $FF;
        Log(tst[enTwo]);
    Codero ergo sum
    // Программирую — значит, существую
      Наваял целый класс в качестве обертки для GetEnumName/GetEnumValue :)

      Функции для преобразования enum <=> Integer/string, каждая перегружена (может принимать предварительно закэшированный OrdType).
      ExpandedWrap disabled
        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;


      Примеры:
      ExpandedWrap disabled
          Log(TEnum<TFormState>.Str(Form1.FormState));
          if Form1.Position = TEnum<TPosition>.Val('poDesigned') then Log('yes');


      К большому сожалению, с множествами фокус не пройдет.

      И не спрашивайте, чем это лучше GetEnumName(TypeInfo(TEnumType), Int(item)) :)
      Codero ergo sum
      // Программирую — значит, существую
      0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
      0 пользователей:


      Рейтинг@Mail.ru
      [ Script Execution time: 0,0977 ]   [ 17 queries used ]   [ Generated: 24.09.18, 05:44 GMT ]