Delphi代码迁移问题

5

在我的代码迁移过程中,我遇到了Delphi 2010和Delphi Berlin(最新更新)之间的问题...... 我编写了一个简单的代码来演示一种奇怪的行为...

我有一个应用程序,使用TList(旧版)和TList(来自Generics.Collections) 我知道下面这段代码对您来说没有任何意义,但这是为了演示目的。

unit Unit1;
interface
uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
  TTest = class
    Name: string;
    constructor Create(Nome: string);
  end;
  TForm1 = class(TForm)
    btn1: TButton;
    procedure btn1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    FList: TList;
  end;
var
  Form1: TForm1;
implementation
uses
  System.Generics.Collections;
{$R *.dfm}

procedure TForm1.btn1Click(Sender: TObject);
var
  tmpList: TList<TTest>;
begin
  tmpList := TList<TTest>.Create;
  tmpList.Add(TTest.Create('A'));
  tmpList.Add(TTest.Create('B'));
  tmpList.Add(TTest.Create('C'));
  tmpList.Add(TTest.Create('D'));
  tmpList.Add(TTest.Create('E'));
  FList := TList(tmpList);
  ShowMessage(TTest(FList[0]).Name);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  FList := TList.Create;
end;

constructor TTest.Create(Nome: string);
begin
  Name := Nome;
end;
end.

在Delphi 2010中,ShowMessage显示“ A”字符,但在Delphi Berlin中会引发访问冲突。

两个应用程序的优化设置均为False。

2个回答

9
FList := TList(tmpList);

这是问题所在。强制转换是错误的,因为tmpList不是TList类型。
您的代码之所以能够编译,只是因为强制转换,但强制转换并没有改变右侧对象不属于要转换的类型这一事实。强制转换只是让编译器停止抱怨并避免您自己犯错。您的强制转换是对编译器的谎言,而运行时错误就是其后果。
这段代码可能在旧版本中可以工作,但只是偶然。您的运气已经改变了。
很难知道如何建议修复。正如您所说,这段代码毫无意义。每次按下按钮时,都会泄漏一个列表。我建议您删除所有强制转换,停止使用非泛型TList,并仅使用泛型列表。

6

TList<T>类无法强制转换为TList

你不能将一个类型强制转换为另一个类型并期望得到合理的结果,就像你不能将TForm强制转换为TButton一样(例如)。

在Delphi中,这种形式的类型转换是不受检查的,有时也称为硬类型转换。也就是说,编译器将简单地相信你知道自己在做什么,并会履行你的要求,但如果类型转换无效,则结果将是不可预测的。

对于对象引用类型(和/或接口引用)之间的转换,可以使用使用as运算符进行检查型转换

FList := tmpList as TList;

如果一个已检查的转换是无效的(比如这个),那么编译器将抛出运行时异常,提醒您有错误。
为什么编译器允许不受检查的转换?
在某些情况下,不受检查的转换可能是有用和安全可靠的,在特定的用例中可以依赖。但在这些特定条件之外,不受检查的转换最多只是相信运气或特定的编译器行为或 RTL 特征,这些特征可能会发生变化。
例如,32 位存储对象引用或其他指针值在 Integer 变量中的技巧。这样的代码可能会在重新编译为 64 位时继续工作,但现在只是一种幸运并且只在某些情况下,因为只有可能的 64 位指针值的子集可以安全地存储在 32 位整数中。
如果您有成功硬转换 TList 和 TList 之间的代码,那么它只能通过运气,作为编译器或 RTL 在那个时候的特定行为的结果。

标签是指针大小,因此倒数第二段放错了位置。它不是Integer,而是NativeInt。 - David Heffernan
不,倒数第二段绝对是正确的。问题在于早先不合适地将Tag作为使用Integer存储指针的错误(实际上是不必要的)示例进行引用。我只是去掉了这个不正确的例子。 - Deltics
现在您已经删除了对Tag的错误引用,这是准确的。实际上,这将是硬转换合理的一个很好的例子。指针和NativeInt之间的转换。 - David Heffernan

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