22

我听说很多程序员,尤其是 Delphi 程序员鄙视使用“with”。

我认为它使程序运行得更快(只有一个对父对象的引用),并且如果使用得当(少于十几行代码并且没有嵌套)更容易阅读代码。

这是一个例子:

procedure TBitmap32.FillRectS(const ARect: TRect; Value: TColor32);
begin
  with ARect do FillRectS(Left, Top, Right, Bottom, Value);
end;

我喜欢使用with. 我怎么了?

4

16 回答 16

33

使用 with 的一个烦恼是调试器无法处理它。所以它使调试更加困难。

一个更大的问题是代码不太容易阅读。特别是如果 with 语句有点长。

procedure TMyForm.ButtonClick(...)
begin
  with OtherForm do begin
    Left := 10;
    Top := 20;
    CallThisFunction;
  end;
end;

哪个 Form 的 CallThisFunction 会被调用?自我(TMyForm)或其他形式?如果不检查 OtherForm 是否具有 CallThisFunction 方法,您将无法知道。

最大的问题是你可以在不知情的情况下轻松解决错误。如果 TMyForm 和 OtherForm 都有 CallThisFunction,但它是私有的怎么办。您可能期望/希望调用 OtherForm.CallThisFunction,但实际上并非如此。如果您不使用 with,编译器会警告您,但现在没有。

在 with 中使用多个对象会使问题成倍增加。见http://blog.marcocantu.com/blog/with_harmful.html

于 2008-09-16T11:44:41.137 回答
12

在这种情况下,我更喜欢 VB 语法,因为在这里,您需要在 with 块内的成员前面加上 a.以避免歧义:

With obj
    .Left = 10
    .Submit()
End With

但实际上,总的来说并没有什么问题with

于 2008-09-16T11:44:34.697 回答
12

如果该with声明可以通过以下方式扩展,那就太好了:

with x := ARect do
begin
  x.Left := 0;
  x.Rigth := 0;
  ...
end;

您不需要声明变量“x”。它将由编译器创建。写起来很快,没有混淆,使用的是哪个函数。

于 2010-03-05T06:38:22.387 回答
8

“with”不太可能使代码运行得更快,编译器更有可能将其编译为相同的可执行代码。

人们不喜欢 "with" 的主要原因是它会引起命名空间范围和优先级的混淆。

在某些情况下,这是一个真正的问题,而在某些情况下,这是一个非问题(非问题情况将在问题中描述为“明智地使用”)。

由于可能的混淆,一些开发人员选择完全避免使用“with”,即使在可能没有这种混淆的情况下也是如此。这可能看起来很教条,但是可以争辩说,随着代码的变化和增长,即使在代码被修改到会使“with”混淆的程度之后,“with”的使用可能仍然存在,因此最好不要首先介绍它的用途。

于 2008-09-16T11:53:33.223 回答
7

实际上:

procedure TBitmap32.FillRectS(const ARect: TRect; Value: TColor32);
begin
  with ARect do FillRectS(Left, Top, Right, Bottom, Value);
end;

procedure TBitmap32.FillRectS(const ARect: TRect; Value: TColor32);
begin
  FillRectS(ARect.Left, ARect.Top, ARect.Right, ARect.Bottom, Value);
end;

将生成完全相同的汇编代码。

with如果子句的值是函数或方法,则可能存在性能损失。在这种情况下,如果您想要良好的维护和良好的速度,只需执行编译器在后台执行的操作,即创建一个临时变量

实际上:

with MyRect do
begin
  Left := 0;
  Right := 0;
end;

由编译器以伪代码编码:

var aRect: ^TRect;

aRect := @MyRect;
aRect^.Left := 0;
aRect^.Right := 0;

然后aRect可以只是一个 CPU 寄存器,但也可以是堆栈上真正的临时变量。当然,我在这里使用指针,因为TRect它是record. 它对对象更直接,因为它们已经是指针。

就个人而言,我有时在我的代码中使用 with,但我几乎每次生成 asm 时都会检查以确保它完成了它应该做的事情。不是每个人都能或有时间去做,所以恕我直言,局部变量是一个很好的替代方案。

我真的不喜欢这样的代码:

for i := 0 to ObjList.Count-1 do
  for j := 0 to ObjList[i].NestedList.Count-1 do
  begin
    ObjList[i].NestedList[j].Member := 'Toto';
    ObjList[i].NestedList[j].Count := 10;
  end;

它仍然很容易阅读:

for i := 0 to ObjList.Count-1 do
  for j := 0 to ObjList[i].NestedList.Count-1 do
  with ObjList[i].NestedList[j] do
  begin
    Member := 'Toto';
    Count := 10;
  end;

甚至

for i := 0 to ObjList.Count-1 do
  with ObjList[i] do
  for j := 0 to NestedList.Count-1 do
  with NestedList[j] do
  begin
    Member := 'Toto';
    Count := 10;
  end;

但如果内部循环很大,则局部变量确实有意义:

for i := 0 to ObjList.Count-1 do
begin
  Obj := ObjList[i];
  for j := 0 to Obj.NestedList.Count-1 do
  begin
    Nested := Obj.NestedList[j];
    Nested.Member := 'Toto';
    Nested.Count := 10;
  end;
end;

这段代码不会比以下代码慢with:编译器实际上是在幕后完成的!

顺便说一句,这将使调试更容易:您可以放置​​一个断点,然后将鼠标指向ObjNested直接获取内部值。

于 2012-09-04T16:14:17.050 回答
3

这种争论也经常发生在 Javascript 中。

基本上,使用 With 语法很难一眼看出您正在调用哪个 Left/Top/etc 属性/方法。您可以有一个名为 Left 的局部变量和一个属性(我已经有一段时间没有完成delphi,如果名称错误,对不起)称为Left,甚至可能是一个名为Left的函数。任何阅读代码但对 ARect 结构不太熟悉的人都可能会非常迷茫。

于 2008-09-16T11:40:48.387 回答
3

你在打字时节省的东西,你会失去可读性。许多调试器也不知道您指的是什么,因此调试更加困难。它不会使程序运行得更快。

考虑使您的 with 语句中的代码成为您所引用对象的方法。

于 2008-09-16T11:48:42.110 回答
3

这主要是一个维护问题。

从语言的角度来看,WITH 的想法是合理的,并且当合理使用时,它可以保持代码更小更清晰的论点具有一定的有效性。然而问题是,大多数商业代码将在其生命周期内由几个不同的人维护,并且最初是一个小的、易于解析的构造,在编写时很容易随着时间的推移变成笨拙的大型结构,其中 WITH 的范围不是维护者很容易解析。这自然会产生错误,并且很难找到错误。

例如,假设我们有一个小函数 foo,其中包含三或四行代码,这些代码已包装在 WITH 块中,那么确实没有问题。然而几年后,这个函数可能已经在几个程序员的带领下扩展为 40 或 50 行代码,仍然包裹在 WITH 中。现在这很脆弱,并且引入错误的时机已经成熟,特别是如果维护者星标引入额外的嵌入式 WITH 块。

WITH 没有其他好处 - 代码应该以完全相同的方式解析并以相同的速度运行(我在 D6 中用于 3D 渲染的紧密循环中对此进行了一些实验,我找不到任何区别)。调试器无法处理它也是一个问题 - 但应该在一段时间前修复它,如果有任何好处,则值得忽略。不幸的是没有。

于 2010-03-04T20:47:10.177 回答
2

我不喜欢它,因为它使调试变得很麻烦。您无法通过将鼠标悬停在变量上来读取变量等的值。

于 2008-09-16T11:38:27.173 回答
2

只要您保持简单并避免歧义,它就没有错。

据我所知,它并没有加快任何速度——它纯粹是语法糖。

于 2008-09-16T11:41:43.780 回答
2

在工作中,我们为从现有 Win 32 代码库中删除 Withs 打分,因为维护使用它们的代码需要额外的努力。我在以前的工作中发现了几个错误,其中一个名为 BusinessComponent 的局部变量被一个带有相同类型的已发布属性 BusinessComponent 的对象的 With begin 块中所掩盖。编译器选择使用已发布的属性,并且本应使用局部变量的代码崩溃了。

我见过像这样的代码

用 a,b,c,d 做 {除了它们是更长的名字,只是在这里缩短) begin i := xyz;
结尾;

试图找到 xyz 的来源可能会很痛苦。如果是 c,我会更早地将其写为

我:= c.xyz;

您认为理解这一点非常简单,但不是在一个 800 行长的函数中,它在一开始就使用了 with !

于 2008-09-16T11:49:20.160 回答
2

你可以结合 with 语句,所以你最终得到

with Object1, Object2, Object3 do
begin
  //... Confusing statements here
end

如果你认为调试器被一个混淆了,我看不出任何人可以确定with块中发生了什么

于 2010-06-27T09:37:30.833 回答
1

它允许不称职或邪恶的程序员编写难以阅读的代码。因此,仅当您既不无能也不邪恶时才使用此功能。

于 2008-09-16T11:46:28.683 回答
1
... 跑得更快 ...

不一定-您的编译器/解释器通常比您更擅长优化代码。

我想这让我说“糟糕!” 因为它很懒——当我阅读代码时(尤其是其他人的),我喜欢看明确的代码。所以我什至会在 Java 中写“this.field”而不是“field”。

于 2008-09-16T11:59:50.743 回答
-1

我们最近在我们的 Delphi 编码标准中禁止了它。

优点经常超过缺点。

那是由于误用而引入了错误。这些并不能证明节省编写或执行代码的时间是合理的。

是的,使用 with 可以(稍微)加快代码执行速度。

在下面, foo 只被评估一次:

with foo do
begin
  bar := 1;
  bin := x;
  box := 'abc';
end

但是,这里它被评估了三遍:

foo.bar := 1;
foo.bin := x;
foo.box := 'abc';
于 2008-09-16T11:57:27.003 回答
-1

对于 Delphi 2005,with-do 语句中存在硬错误 - 评估指针丢失并用指针向上替换。必须使用局部变量,而不是直接使用对象类型。

于 2010-07-27T09:05:22.730 回答