泛型和Marshal / UnMarshal。我在这里漏掉了什么?

4
我需要提醒一下: 我正在使用Delphi XE2 - 但XE或2010也应该可以胜任 :-) 此问题现在已经在Quality Central QC#99313 中,请投票支持它 :-) 截至2011年10月20日,Embarcadero已将QC报告标记为已解决。 解决方案由SilverKnight提供。但Embarcadero缺乏信息让我感到担忧。因为该解决方案建议使用其他源代码而不是XE(2)帮助系统、其他论坛和CC中解释的源代码。但请自行查看QC。 给定以下类型声明:
type
TTestObject : Class
    aList : TStringList;
    function Marshal : TJSonObject;
  end;

  TTestObjectList<T:TestObject> : Class(TObjectList<T>)
    function Marshal : TJSonObject; // How to write this ? 
  end;

我希望能为TTestObjectList实现一个Marshal方法。 据我所知,我应该为TTestObject注册一个转换器,并为其每个元素调用Marshal以使其更加美观。
TTestObject的Marshal会注册以下转换器:
RegisterConverter(TStringList,
  function(Data: TObject): TListOfStrings
  var
    i, Count: Integer;
  begin
    Count := TStringList(Data).Count;
    SetLength(Result, Count);
    for i := 0 to Count - 1 do
      Result[i] := TStringList(Data)[i];
  end);

通用的 TTestObjectList Marshal 方法:
function TTestObjectList<T>.Marshal: TJSONObject;
var
Mar : TJsonMarshal;  // is actually a property on the list.
begin
  Mar := TJsonMarshal.Create(TJSONConverter.Create);
  try
    RegisterConverter(TTestObject,
      function(Data: TObject): TObject
      begin
        Result := TTestObject(Data).Marshal;
      end);
    Result := Mar.Marshal(Self) as TJSONObject;
  finally
    Mar.Free;
  end;
end;

这是使用列表的简化示例。

var
  aTestobj : TTestObject; 
  aList : TTestObjectList<TTestObject>;
  aJsonObject : TJsonObject;
begin
  aTestObj := TTestObject.Create; // constructor creates and fills TStringlist with dummy data.
  aJsonObject := aTestObj.Marshal; // This works as intended.

  aList := TTestObjectList<TTestObject>.Create;
  aJsonObject := aList.Marshal; // Fails with tkpointer is unknown .... 
end;

当然,我也有类似的恢复(解组)功能。但是上面的代码应该可以工作 - 至少据我所知。
所以,如果有人能指出:
为什么List无法进行编组?
我知道我的列表上有TJsonMarshal属性 - 但它也有一个转换器/还原器。
改用TTypeStringConverter(而不是TTypeObjectConverter)将返回有效的字符串。但是我喜欢一直在TJsonObject上工作的想法。否则,当从字符串解组到TTestObject时,我会遇到相同的问题(或类似的问题)。
欢迎任何建议/想法。

我简直不敢相信Embarcadero把那个问题标记为已解决 --- 这只是一个权宜之计 - 如果真的解决了,他们就不会有一个允许你传入自己实例的重载构造函数!或者,在这种特殊情况下,对象应该在使用任何一个构造函数时都能正常工作。我认为仍然存在一个bug。我已经在QA中发布了帖子,要求他们重新考虑将其关闭为“用户错误”。 - SilverKnight
2个回答

4
以下是关于解决 Delphi XE2 运行时错误的“变通方法”(我能够复制相同的运行时错误):
请注意,在创建 Marshal 变量时,以下代码在项目中定义:
Marshal := TJSONMarshal.Create(TJSONConverter.Create);

将两行代码修改为以下内容:

将这两行代码都修改为:

Marshal := TJSONMarshal.Create;  //use the default constructor - which does the same thing as TJSONConvert.Create already

解决问题-由TOndrej提供的测试应用程序可以无错误运行。


+1 给你 :-) ... 我必须承认,使用 TJSONMarshal.Create 和构造函数是一种习惯。 但我不知道它实际上可以在没有它的情况下完成相同的工作.. 谢谢你提供这个信息。 - Bimmer_R

2

我不确定你为什么会出现那个错误。以下内容在Delphi XE中对我有效:

program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils, Classes, Contnrs,
  Generics.Defaults, Generics.Collections,
  DbxJson, DbxJsonReflect;

type
  TTestObject = class(TObject)
    aList : TStringList;
    function Marshal : TJSonObject;
  public
    constructor Create;
    destructor Destroy; override;
  end;

  TTestObjectList<T:TTestObject,constructor> = class(TObjectList<T>)
    function Marshal: TJSonObject;
    constructor Create;
  end;

{ TTestObject }

constructor TTestObject.Create;
begin
  inherited Create;
  aList := TStringList.Create;
  aList.Add('one');
  aList.Add('two');
  aList.Add('three');
end;

destructor TTestObject.Destroy;
begin
  aList.Free;
  inherited;
end;

function TTestObject.Marshal: TJSonObject;
var
  Marshal: TJSONMarshal;
begin
  Marshal := TJSONMarshal.Create(TJSONConverter.Create);
  try
    Marshal.RegisterConverter(TStringList,
      function (Data: TObject): TListOfStrings
      var
        I, Count: Integer;
      begin
        Count := TStringList(Data).Count;
        SetLength(Result, Count);
        for I := 0 to Count - 1 do
          Result[I] := TStringList(Data)[I];
      end
      );
    Result := Marshal.Marshal(Self) as TJSONObject;
  finally
    Marshal.Free;
  end;
end;

{ TTestObjectList<T> }

constructor TTestObjectList<T>.Create;
begin
  inherited Create;
  Add(T.Create);
  Add(T.Create);
end;

function TTestObjectList<T>.Marshal: TJSonObject;
var
  Marshal: TJsonMarshal;
begin
  Marshal := TJSONMarshal.Create(TJSONConverter.Create);
  try
    Marshal.RegisterConverter(TTestObject,
      function (Data: TObject): TObject
      begin
        Result := T(Data).Marshal;
      end
      );
    Result := Marshal.Marshal(Self) as TJSONObject;
  finally
    Marshal.Free;
  end;
end;

procedure Main;
var
  aTestobj : TTestObject;
  aList : TTestObjectList<TTestObject>;
  aJsonObject : TJsonObject;
begin
  aTestObj := TTestObject.Create;
  aJsonObject := aTestObj.Marshal;
  Writeln(aJsonObject.ToString);

  Writeln;
  aList := TTestObjectList<TTestObject>.Create;
  aJsonObject := aList.Marshal;
  Writeln(aJsonObject.ToString);

  Readln;
end;

begin
  try
    Main;
  except
    on E: Exception do
    begin
      ExitCode := 1;
      Writeln(Format('[%s] %s', [E.ClassName, E.Message]));
    end;
  end;
end.

刚刚在XE中尝试了你的代码,我必须承认,它有效。我需要用它尝试一些更复杂的事情。给我几个小时,我会回来的。我想用一个稍微大一点的项目进行调查,看看是否会出现问题。 - Bimmer_R
再次您好,抱歉耽搁了。我已经在XE2中测试了代码...结果与我的代码一样失败了 ???? 也许XE2中有关于封送的问题? 我对通用类不太了解——您在类型声明中包含构造函数的方法真的很出色。仅凭这一点,我愿意接受您的代码作为答案——即使它只适用于XE :-) - Bimmer_R
1
问题现已升级至QC#99313。附带TOndrej的代码示例。 - Bimmer_R

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