如何在Delphi中从通用类型转换为变体类型?

13

我有一个Delphi泛型类,其中暴露了一个带有泛型类型参数的函数。在这个函数内部,我需要将泛型类型的实例传递给另一个期望Variant类型的对象。类似于这样:

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;

我尝试使用Rtti.TValue.From(SomeValue).AsVariant。这对于整数类型有效,但对于布尔类型则会出问题。 我不太明白为什么,因为通常我应该能够将布尔值分配给变体...

有更好的方法进行此转换吗? 我只需要它适用于简单的内置类型(不包括枚举和记录)


你尝试过创建一个类型为“Variant”的本地变量,将“SomeValue”分配给它,然后将本地变量传递给“FMyIntf.DoStuff()”吗? - Jerry Gagnon
是的,我做不到,因为从'T'到'Variant'没有有效的转换。 - Mathias Falkenberg
3个回答

11

我认为没有直接将通用类型转换为变体的方法,因为变体无法容纳所有可能的类型。您必须编写特定的转换程序。例如:

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;

TValue.AsVariant 处理大部分类型转换,因此可以简化此函数。我将处理枚举以防以后需要使用:

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;

可能的使用方式:

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

我担心会是这样的情况 :-P 在 TValue 的内部进行了一些探索后,发现 Boolean 的 TypeKind 是 tkEnumeration,并且当调用具有 TypeKind tkEnumeration 的值的 AsVariant 时,TValue 会引发异常。这也意味着你的示例 - 尽管它不会引发异常 - 仍然返回错误的 Variant,因为我的 Boolean 被转换为 'AsOrdinal',导致一个整数类型的 Variant - 而不是布尔类型...也许我需要检查类型和类型名称两者... - Mathias Falkenberg
@MathiasFalkenberg 我已经编辑了我的答案以正确处理布尔值。 - Linas
+1. 您可以通过将尽可能多的类型转换为某种可变表示形式来制作最佳的GenericToVariant。如果需要,您可以添加代码来转换重要的本地类(TObject)类型,使用新的TObject.ToString方法。 - Warren P
似乎只有布尔值是个问题。我想如果在@Linas的例子中缩短case语句只考虑tkEnumeration和其他所有情况是否就足够了。TValue.AsVariant会为不支持的类型抛出自己的异常,而tkInteger、tkInt64、tkFloat、tkString等等似乎可以使用TValue.AsVariant进行转换... - Mathias Falkenberg
实际上,这样做不就可以了吗?if val.TypeInfo = TypeInfo(Boolean) then Result := val.AsBoolean else Result := val.AsVariant; - Mathias Falkenberg
显示剩余2条评论

1

看起来在我的Delphi 10.2版本中,布尔问题已经解决了,TValue.From<T>(FValue).AsVariant就足够了。

这里有一个例子,还有一些其他有用的东西,例如比较泛型类型:

  TMyValue<T> = class(TPersistent)
  private
    FValue: T;
    procedure SetValue(const AValue: T);
    function GetAsVariant: Variant; override;
  public
    procedure Assign(Source: TPersistent); override;
    property Value: T read FValue write SetValue;
    property AsVariant: Variant read GetAsVariant;
  end;

function TMyValue<T>.GetAsVariant: Variant;
begin
  Result:= TValue.From<T>(FValue).AsVariant;
end;

procedure TMyValue<T>.SetValue(const AValue: T);
begin
  if TEqualityComparer<T>.Default.Equals(AValue, FValue) then Exit;
  FValue:= AValue;
  //do something
end;

procedure TMyValue<T>.Assign(Source: TPersistent);
begin
  if Source is TMyValue<T> then Value:= (Source as TMyValue<T>).Value
  else inherited;
end;

0
另外一种方式(测试过XE10)
Var
  old : variant;
  val : TValue;
Begin
  val := TValue.FromVariant(old);
End;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接