4

I'm trying to convert a generic variable of type T into string.

  TMyTest = class
    class function GetAsString<T>(const AValue : T) : string; static;
  end;

...

uses
  System.Rtti;

class function TMyTest.GetAsString<T>(const AValue : T) : string;
begin
  Result := TValue.From<T>(AValue).ToString();
end;

It works good using several types (like Integer, Double, Boolean...) but it "fails" using Variant variables.

procedure TForm1.FormCreate(Sender: TObject);
var
  Tmp : Variant;
begin
  Tmp := 123;

  ShowMessage(TMyTest.GetAsString<Variant>(Tmp));
end;

It produces the following output:

(variant)

I was expecting the same output obtained by the VarToStr function (But I cannot use that function with generic variables):

123

Fabrizio
  • 7,603
  • 6
  • 44
  • 104

2 Answers2

6

You can check whether T is variant and then use VarToStr on AsVariant function.

You can easily extend that function to cater for other types where ToString will not give you expected result.

uses
  System.TypInfo, System.Rtti;

class function TMyTest.GetAsString<T>(const AValue : T) : string;
begin
  if PTypeInfo(TypeInfo(T)).Kind = tkVariant then
    Result := VarToStr(TValue.From<T>(AValue).AsVariant)
  else
    Result := TValue.From<T>(AValue).ToString();
end;
Dalija Prasnikar
  • 27,212
  • 44
  • 82
  • 159
  • 1
    `If TypeInfo(T) = TypeInfo(Variant) then` would be better. Or, in XE7+, you can use `GetTypeKind()` instead. Both approaches will allow the compiler to discard the unused branch at compile time, and thus you should be able to use `VarToStr(AValue)`without involving `TValue`. Checking the `PTypeInfo.Kind` will not allow that, as it requires a runtime check, so both branches must be compiled. – Remy Lebeau Aug 25 '21 at 15:26
  • 1
    @RemyLebeau Can you add that as separate answer. I keep my code compatible with XE4, so I keep forgetting about existence of other, better options. – Dalija Prasnikar Aug 25 '21 at 16:36
3

You can check the type of T using RTTI, and then call VarToStr() when T is a Variant, eg:

class function TMyTest.GetAsString<T>(const AValue : T) : string;
begin
  if TypeInfo(T) = TypeInfo(Variant) then begin
    // yes, the following cast looks odd, but the compiler can't validate
    // T is really a Variant in this context, as it syntax-checks the code 
    // *before* instantiating the Generic, so a runtime cast is needed.
    // The TypeInfo check above will ensure the cast is safe...
    Result := VarToStr({AValue}PVariant(@AValue)^);
  end else begin
    Result := TValue.From<T>(AValue).ToString;
  end;
end;

Or, in XE7+, you can use the GetTypeKind() intrinsic instead:

class function TMyTest.GetAsString<T>(const AValue : T) : string;
begin
  if GetTypeKind(T) = tkVariant then begin
    Result := VarToStr({AValue}PVariant(@AValue)^);
  end else begin
    Result := TValue.From<T>(AValue).ToString;
  end;
end;

Either approach will allow the compiler to optimize the code by dropping the unused branch from the executable, as both comparisons are treated as compile-time constants.


Note: other TTypeKind values types that TValue.ToString does not support are tkUnknown, tkArray, tkRecord, and tkDynArray.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • `Result := TValue.FromVariant(AValue).AsString;` or `Result := VarToStr(AValue);` don't not work in any version because `E2010 Incompatible types: 'Variant' and 'T'` so you still need to use typecasting or conversion with `TValue`. – Dalija Prasnikar Aug 25 '21 at 17:58
  • `Result := VarToStr(AValue); ` should work in the XE7+ version. In the pre-XE7 version, even though `TypeInfo(T) = TypeInfo(Variant)` can be validated at compile-time, I guess the compiler still doesn't know `T` is a `Variant`, so a type-cast is needed. – Remy Lebeau Aug 25 '21 at 18:04
  • That is what I thought, but compiler does not agree. I tried with 10.3.3 and 10.4.2, and of course XE4. I don't have versions in between to test whether at some point it worked. – Dalija Prasnikar Aug 25 '21 at 18:11
  • I don't have *any* version installed right now to test with, but I could have sworn this worked at some point. Maybe I'm mistaken. Oh well... – Remy Lebeau Aug 25 '21 at 18:19
  • Maybe Fabrizio can test whether `Result := VarToStr(AValue);` works in XE7, it would be good to know if it worked at some point because then we can file regression bug report. And it was the first thing I wrote, but I cannot say whether I expected it to work because similar things work in some other languages I am using, or I saw it in Delphi, too. – Dalija Prasnikar Aug 25 '21 at 18:22
  • 1
    @DalijaPrasnikar "*it would be good to know if it worked at some point*" - [apparently not](https://stackoverflow.com/a/59905171/65863). – Remy Lebeau Aug 25 '21 at 18:27
  • Both solutions in the answer work in XE7. `Result := VarToStr(AValue);` doesn't work instead (the compiler raise an `E2010 Incompatible types: 'Variant' and 'T'`) – Fabrizio Aug 26 '21 at 07:08