【问题标题】:Why variables are declared as TStrings and created as TStringList?为什么将变量声明为 TStrings 并创建为 TStringList?
【发布时间】:2026-01-11 01:05:02
【问题描述】:

为什么变量被声明为TStrings并创建为TStringList

例如:var sl 被声明为 TStrings 但创建为 TStringList

var
  sl : TStrings;
begin
  sl := TStringList.Create;

  // add string values...
  sl.Add( 'Delphi' );
  sl.Add( '2.01' );

  // get string value using its index
  // sl.Strings( 0 ) will return
  //   'Delphi'
  MessageDlg(
    sl.Strings[ 0 ],
    mtInformation, [mbOk], 0 );

  sl.Free;
end;

【问题讨论】:

  • 我的首要原因:TStrings 涉及更少的输入 :)
  • @mjn 为什么不一路添加TSL = TStringList 到包含在每个单元中的包含文件...... ;-)
  • 很好的问题!!我相信这个问题的根源在于VCL源代码通常使用根祖先类型作为变量,例如TControl。编码人员(包括我)倾向于根植每个变量,但这没有意义。感谢您让我思考。
  • 我认为始终使用尽可能少的特定类型并不是一个坏主意。这可以防止不必要的依赖。那么,如果它没有害处,为什么不坚持这条规则,即使在这种情况下也这样做呢?
  • @smasher 在相关代码中可以避免哪些依赖关系。我可以在函数体中看到 TStringList。

标签: delphi tstringlist


【解决方案1】:

TStrings 是一种抽象类型,并未实现所有方法。

TStringListTStrings 的后代并实现所有功能。在您的代码中,您也可以将变量声明为TStringList

但是,例如在函数定义上,接受TStrings 参数而不是TStringList 是有意义的:

procedure doSomething(lst: TStrings);

这使函数能够与TStrings 的所有实现一起使用,而不仅仅是TStringList

【讨论】:

    【解决方案2】:

    在我看来这是毫无意义的,尽管完全无害。你完全可以将sl 声明为TStringList,我会一直这样做。对于代码的读者来说,它使局部变量列表更容易理解。

    在这段代码中,sl 总是被分配一个TStringList 实例,因此将sl 声明为具有TStrings 的基类类型没有任何好处。但是,如果您的代码为变量分配了各种不同类型的 TStrings 后代,那么将其声明为 TStrings 是有意义的。

    您可能将变量声明为TStrings 类型的情况通常是代码未显式创建实例的情况。例如,接收字符串列表作为参数的实用程序方法如果接受TStrings 会更有用,因为这样可以将任何后代传递给它。这是一个简单的例子:

    procedure PrintToStdOut(Strings: TStrings);
    var
      Item: string;
    begin
      for Item in Strings do
        Writeln(Item);
    end;
    

    显然,当参数被声明为TStrings 而不是TStringList 时,这更有用。

    但是,问题中的代码不是这种性质,我相信如果将 sl 声明为 TStringList 类型,它会得到如此温和的改进。

    【讨论】:

    • 正如你所指出的(但没有描述得太好),这是一个好的设计的原因是它允许你在PrintToStdOut中使用任何TStrings后代,所以TStringList,@ 987654339@、ListBox1.Items 等运行良好。声明它接受TStringList 将意味着最后两个调用将失败。
    【解决方案3】:

    因为这样你可以在SL 变量中放置另一个TStrings 后代(我可能会称之为Strings,而不是SL)。

    在您的情况下,这没有实际意义,因为围绕 SL 的逻辑包含 TStringList 的创建,并且没有外部赋值或参数解析。

    但是,如果您曾经将逻辑从分配中分离出来,那么您可以从使用任何 TStrings 后代中受益。

    例如,TMemoy.LinesTListBox.ItemsTComboBox.Items 等。
    从外部看,它们看起来像是TStrings,但在内部它们不使用TStringList,而是使用它们自己的后代。

    一些源自TStrings的类示例:

    source\DUnit\Contrib\DUnitWizard\Source\DelphiExperts\Common\XP_OTAEditorUtils.pas:
         TXPEditorStrings = class(TStrings)
    source\fmx\FMX.ListBox.pas:
           TListBoxStrings = class(TStrings)
    source\fmx\FMX.Memo.pas:
         TMemoLines = class(TStrings)
    source\rtl\common\System.Classes.pas:
         TStringList = class(TStrings)
    source\vcl\Vcl.ComCtrls.pas:
         TTabStrings = class(TStrings)
         TTreeStrings = class(TStrings)
         TRichEditStrings = class(TStrings)
    source\vcl\Vcl.ExtCtrls.pas:
         TPageAccess = class(TStrings)
         THeaderStrings = class(TStrings)
    source\vcl\Vcl.Grids.pas:
         TStringGridStrings = class(TStrings)
         TStringSparseList = class(TStrings)
    source\vcl\Vcl.Outline.pas:
         TOutlineStrings = class(TStrings)
    source\vcl\Vcl.StdCtrls.pas:
         TCustomComboBoxStrings = class(TStrings)
         TMemoStrings = class(TStrings)
         TListBoxStrings = class(TStrings)
    source\vcl\Vcl.TabNotBk.pas:
         TTabPageAccess = class(TStrings)
    

    【讨论】:

      【解决方案4】:

      TStringList 是抽象 TStrings 类的具体实现

      【讨论】:

      • 确实如此,但它没有回答问题。
      • 确实如此,如果您知道为什么拥有抽象基类是件好事,同样的原因有时拥有一个接口是个好主意。在 delphi 中,抽象基类是没有引用计数的单继承接口。