34

我一直在阅读有关 delphi 中with关键字的坏事,但在我看来,如果您不过度使用它。它可以让你的代码看起来很简单。

我经常将我所有的 TClientDataSets 和 TFields 放在 TDataModules 中。所以在我的表格中我有这样的代码

procedure TMyForm.AddButtonClick(Sender: TObject);
begin  
  with LongNameDataModule do
  begin
     LongNameTable1.Insert;
     LongNameTable1_Field1.Value := "some value";
     LongNameTable1_Field2.Value := LongNameTable2_LongNameField1.Value;
     LongNameTable1_Field3.Value := LongNameTable3_LongNameField1.Value;
     LongNameTable1_Field4.Value := LongNameTable4_LongNameField1.Value;
     LongNameTable1.Post;
  end
end;

没有with关键字我必须写这样的代码

    procedure TMyForm.AddButtonClick(Sender: TObject);
    begin            
      LongNameDataModule.LongNameTable1.Insert;
      LongNameDataModule.LongNameTable1_LongNameField1.Value := "some value";

      LongNameDataModule.LongNameTable1_LongNameField2.Value :=
               LongNameDataModule.LongNameTable2_LongNameField1.Value;

      LongNameDataModule.LongNameTable1_LongNameField3.Value :=
               LongNameDataModule.LongNameTable3_LongNameField1.Value;

      LongNameDataModule.LongNameTable1_LongNameField4.Value :=
               LongNameDataModule.LongNameTable4_LongNameField1.Value;

      LongNameDataModule.LongNameTable1.Post;
    end;

我认为使用with关键字更容易阅读。

我应该避免使用with关键字吗?

4

14 回答 14

61

除了像“A、B、C、D”这样的病态条件之外,with 的最大危险是你的代码可以在不通知你的情况下默默地改变含义。考虑这个例子:

with TFoo.Create
try
  Bar := Baz;
  DoSomething();
finally
  Free;
end;

您编写此代码时知道 Bar 是 TFoo 的属性,而 Baz 是包含具有此代码的方法的类型的属性。

现在,两年后,一些好心的开发人员进来为 TFoo 添加了 Baz 属性。您的代码默默地改变了含义。编译器不会抱怨,但代码现在已损坏。

于 2009-02-05T13:46:07.320 回答
32

with关键字是一个很好的功能,可以使您的代码更具可读性,但也有一些缺陷。

调试:

使用这样的代码时:

with TMyClass.Create do
try
  Add('foo');
finally
  Free;
end;

无法检查此类的属性,因此始终声明一个变量并在其上使用with关键字。

接口:

with子句中创建接口时,它会一直存在到您的方法结束:

procedure MemoryHog;
begin
  with GetInterfaceThatTakes50MBOfMemory do
    Whatever;
  ShowMessage('I''m still using 50MB of memory!');
end;

明晰

当在with子句中使用具有范围内已存在的属性或方法名称的类时,它很容易欺骗您。

with TMyForm.Create do
  Width := Width + 2; //which width in this with is width?

当然,当名称重复时,您使用的是在 with 语句 (TMyForm) 中声明的类的属性和方法。

于 2009-02-05T09:01:47.677 回答
23

with声明有其位置,但我必须同意过度使用会导致代码模棱两可。一个好的经验法则是确保在添加 with 语句后代码“更”可读和可维护。如果您觉得在添加语句后需要添加注释来解释代码,那么这可能是个坏主意。如果代码在您的示例中更具可读性,请使用它。

顺便说一句:这一直是我在 Delphi 中最喜欢的模式之一,用于显示模态窗口

with TForm.Create(nil) do
try
  ShowModal;
finally
  Free;
end
于 2009-02-05T04:02:03.583 回答
15

我倾向于完全禁止 with 声明。如前所述,它会使事情变得复杂,我的经验是它会。很多时候,调试器想要评估值是因为 withs,而且我经常发现嵌套的 withs 会导致代码难以阅读。

Brian 的代码看起来可读且漂亮,但如果您直接对发送者进行类型转换,代码会更短,并且您消除了对您启用的组件的所有疑问:

TAction(Sender).Enabled := Something;

如果您担心输入太多,我建议对长名称对象进行临时引用:

var
  t: TTable;
begin
  t := theLongNamedDataModule.WithItsLongNamedTable;
  t.FieldByName(' ');
end;

不过,我不明白为什么打字会打扰你。我们首先是打字员,其次是程序员,代码完成、复制粘贴和按键记录可以帮助您成为更有效的打字员。

更新:刚刚偶然发现一篇长篇文章,其中有一小部分关于 with-statements:he with 关键字。该语言中最可怕、最危险、最令人发指的功能。:-)

于 2009-02-05T08:40:30.917 回答
8

当我第一次开始 pascal 编程(使用 TurboPascal!)并边走边学时,WITH 看起来很棒。正如您所说,这是繁琐打字的答案,是那些长记录的理想选择。自从 Delphi 到来以来,我一直在删除它并鼓励其他人放弃它 - Verity 在寄存器中巧妙地总结了 除了降低可读性之外,还有两个主要原因我会避免它:

  1. 如果你使用一个类,那么无论如何你都不需要它——只有记录“似乎”从中受益。
  2. 使用调试器通过 Ctrl-Enter 跟随代码到声明不起作用。

也就是说,为了便于阅读,我仍然使用以下语法:

procedure ActionOnUpdate( Sender : TObject )
begin
  With Sender as TAction do
    Enabled := Something
end;

我还没有看到更好的构造。

于 2009-02-05T06:51:45.053 回答
6

在我看来,您的示例(在按钮单击中访问数据模块)是一个设计不佳的示例。如果您将此代码移到它应该在的数据模块中,那么对 WITH 的全部需求就会消失。OnClick 然后只调用 LongNameDataModule.InsertStuff 并且不需要。

With 是一个糟糕的设备,您应该查看您的代码以了解您为什么需要它。你可能做错了什么,或者可以做得更好。

于 2009-02-05T09:12:46.000 回答
6

正如 Vegar 所提到的,使用临时引用同样简洁、可读性更强、更易于调试,并且不太容易出现隐蔽问题。

到目前为止,我从未发现需要使用with。我曾经对它很矛盾,直到我接手了一个经常使用头脑弯曲的项目。质疑最初的开发者是打算在第一个还是第二个中引用项目如果那个模棱两可的引用是一个带有滑动或笨拙的代码,尝试调试它的痛苦,以及扩展或修改使用这些的类的影响可憎的事不值得任何人花时间。

显式代码更具可读性。这样你就可以吃蛋糕并享受它。

procedure TMyForm.AddButtonClick(Sender: TObject);
var
  dm: TLongNameDataModuleType
begin  
  dm:=LongNameDataModule;

  dm.LongNameTable1.Insert;
  dm.LongNameTable1_Field1.Value := "some value";
  dm.LongNameTable1_Field2.Value := LongNameTable2_LongNameField1.Value;
  dm.LongNameTable1_Field3.Value := LongNameTable3_LongNameField1.Value;
  dm.LongNameTable1_Field4.Value := LongNameTable4_LongNameField1.Value;
  dm.LongNameTable1.Post;
end;
于 2012-01-17T09:50:37.147 回答
5

我坚信在 Delphi 中删除 WITH 支持。您使用具有命名字段的数据模块的示例用法是我可以看到它起作用的唯一实例。否则,反对它的最佳论点是 Craig Stuntz 给出的——我投了赞成票。

我只是想指出,随着时间的推移,您最终可能(应该)重新处理 OnClick 事件中的所有编码,并且您的代码最终也将从数据模块上的命名字段迁移到使用包装此数据的类,并且使用 WITH 的原因将消失.

于 2009-02-09T02:19:15.247 回答
5

您的问题是“锤子并不总是解决方案”的一个很好的例子。

在这种情况下,'with' 不是你的解决方案:你应该把这个业务逻辑从你的表单移到你的数据模块中。不这样做违反了德墨忒耳法则,就像mghie (Michael Hieke) 已经评论过的那样。

也许您的示例只是说明性的,但如果您实际上在项目中使用这样的代码,那么您应该这样做:

procedure TLongNameDataModule.AddToLongNameTable1(const NewField1Value: string);
begin  
  LongNameTable1.Insert;
  LongNameTable1_Field1.Value := NewField1Value;
  LongNameTable1_Field2.Value := LongNameTable2_LongNameField1.Value;
  LongNameTable1_Field3.Value := LongNameTable3_LongNameField1.Value;
  LongNameTable1_Field4.Value := LongNameTable4_LongNameField1.Value;
  LongNameTable1.Post;
end;

然后像这样从您的表单中调用它:

procedure TMyForm.AddButtonClick(Sender: TObject);
begin  
  LongNameDataModule.AddToLongNameTable1('some value');
end;

这有效地摆脱了您的 with 语句,同时使您的代码更易于维护。

当然,用单引号包围 Delphi 字符串也有助于使其编译;-)

于 2009-04-29T07:41:30.643 回答
3

"with" 的主要问题是你不知道它的作用域在哪里结束,并且你可能有多个重叠的 with 语句。

只要您的代码可读,我认为您不应该避免使用它。

使其更具可读性(并且在更长的代码中减少混淆)的建议之一是,如果 codegear 添加了允许在 with 中使用别名的选项,并且可能允许在一个中使用多个 with:

procedure TMyForm.AddButtonClick(Sender: TObject);
begin  
  with LongNameDataModule as dm, dm.LongNameTable1 as t1, dm.LongNameTable2 as t2 do
  begin
    t1.Insert;
    t1.FieldByName('Field1').AsString := 'some value';
    t1.FieldByName('Field2').AsString := t2.FieldByName('Field2').AsString;
    t1.Post;
    dm.Connection.Commit;
  end
end;
于 2009-02-05T04:07:29.610 回答
3

就我而言,在您提供的情况下,With 是完全可以接受的。它肯定会提高代码的清晰度。

真正的邪恶是当您同时打开多个 with 时。

另外,我的观点是,你使用 with on 会产生很大的不同。如果它是一个真正不同的对象,那么 with 可能是个坏主意。然而,我不喜欢在一个层次上拥有很多变量,即使这是有意义的——通常是包含整个非常复杂的数据项的数据对象——通常是程序设计用于处理的整个工作。(我认为这种情况不会发生在没有此类项目的应用程序中。)为了让世界更清晰,我经常使用记录来对相关项目进行分组。我发现我使用的几乎所有 withs 都是用于访问此类子组。

于 2009-02-05T04:42:44.970 回答
2

关于为什么 with 语句不好,这里有很多很好的答案,所以我会尽量不重复。多年来我一直在使用 with 语句,但我开始回避它。这部分是因为很难确定范围,但我最近开始着手重构,并且没有一个自动重构与 with 语句一起工作 - 并且自动重构很棒。

前段时间,我制作了一个视频,说明为什么 with 语句不好,这不是我最好的作品之一,但在这里

于 2009-02-05T22:10:48.643 回答
0

仅临时使用with(就像您临时注释掉一样)。

它可以帮助您编写代码草图以快速编译和运行。如果您合并解决方案,请清理它!with将代码移动到正确的位置时删除。

于 2018-05-08T11:47:05.667 回答
-1

当前的 With 语句是“危险的”,但可以大幅改进:

  With TForm1.Create (Nil) Do  // New TForm1 instance
    Try
      LogForm (");  // That same instance as parameter to an outer method
      "ShowModal;  // Instance.ShowModal
    Finally
      "Free;  // Instance.Free
    End;

我的建议是:

  1. 每个 With 标头不超过一个对象/记录。
  2. 不允许嵌套 Withs。
  3. 使用 " 表示对象/记录(双引号类似于同上标记:http ://en.wikipedia.org/wiki/Ditto_mark )。
于 2014-03-15T18:52:15.397 回答