Search code examples
delphigenericsdelphi-xerttivariant

How can I convert from generic to Variant in Delphi


I have a Delphi generic class that exposes a function with an argument of the generic type. Inside this function, I need to pass an instance of the generic type on to another object expecting a Variant type. Similar to this:

type
  IMyInterface = interface
    DoStuff(Value: Variant);
  end;      

  TMyClass<T> = class
    FMyIntf: IMyInterface
    procedure DoStuff(SomeValue: T);
  end;

[...]

procedure MyClass<T>.DoStuff(SomeValue: T);
begin
  FMyIntf.DoStuff((*convert SomeValue to Variant here*));
end;

I tried using Rtti.TValue.From(SomeValue).AsVariant. This worked for integral types, but blew up for Booleans. I don't quite see why, since normally I'd be able to assign a Boolean value to a Variant...

Is there a better way to make this conversion? I only need it to work for simple built-in types (excluding enumerations and records)


Solution

  • I think there is no direct way to convert generic type to variant because variant cannot hold all the possible types. You must write your specific conversion routine. E.g.:

    interface
    //...
    type
      TDemo = class
      public
        class function GetAsVariant<T>(const AValue: T): Variant;
      end;
    //...
    implementation
    uses
      Rtti,
      TypInfo;
    //...
    
    { TDemo}
    
    class function TDemo.GetAsVariant<T>(const AValue: T): Variant;
    var
      val: TValue;
      bRes: Boolean;
    begin
      val := TValue.From<T>(AValue);
      case val.Kind of
        tkInteger: Result := val.AsInteger;
        tkInt64: Result := val.AsInt64;
        tkEnumeration: 
        begin
          if val.TryAsType<Boolean>(bRes) then
            Result := bRes
          else
            Result := val.AsOrdinal;
        end;
        tkFloat: Result := val.AsExtended;
        tkString, tkChar, tkWChar, tkLString, tkWString, tkUString:
          Result := val.AsString;
        tkVariant: Result := val.AsVariant
        else
        begin
          raise Exception.Create('Unsupported type');
        end;
      end;
    end;
    

    Because TValue.AsVariant handles most of the type conversions internally, this function can be simplified. I will handle enumerations in case you could need them later:

    class function TDemo.GetAsVariant<T>(const AValue: T): Variant;
    var
      val: TValue;
    begin
      val := TValue.From<T>(AValue);
      case val.Kind of
        tkEnumeration:
        begin
          if val.TypeInfo = TypeInfo(Boolean) then
            Result := val.AsBoolean
          else
            Result := val.AsOrdinal;
        end
        else
        begin
          Result := val.AsVariant;
        end;
      end;
    

    Possible usage:

    var
      vValue: Variant;
    begin
      vValue := TDemo.GetAsVariant<Boolean>(True);
      Assert(vValue = True); //now vValue is a correct Boolean