为什么我不应该在Delphi中使用” with”?

为什么我不应该在Delphi中使用” with”?

Why should I not use “with” in Delphi?

我听说很多程序员,尤其是Delphi程序员都对'with'的使用表示反感。

我认为它可以使程序运行更快(仅对父对象有一个引用),并且如果明智地使用它,则更容易阅读代码(少于十几行代码且没有嵌套)。

这里是一个例子:

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

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


使用with的一个烦恼是调试器无法处理它。因此,这使调试更加困难。

更大的问题是,阅读代码不太容易。尤其是在with语句更长的情况下。

1
2
3
4
5
6
7
8
procedure TMyForm.ButtonClick(...)
begin
  with OtherForm do begin
    Left := 10;
    Top := 20;
    CallThisFunction;
  end;
end;

将调用哪个Form的CallThisFunction?自我(TMyForm)还是OtherForm?不检查OtherForm是否具有CallThisFunction方法就无法知道。

最大的问题是,您甚至可以在不知道的情况下轻松实现错误。如果TMyForm和OtherForm都具有CallThisFunction却是私有的,该怎么办。您可能希望/希望调用OtherForm.CallThisFunction,但实际上不是。如果您不使用with,编译器会警告您,但现在不使用。

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


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

1
2
3
4
With obj
    .Left = 10
    .Submit()
End With

但是实际上,with并没有什么问题。


如果将with语句扩展为以下方式,那就太好了:

1
2
3
4
5
6
with x := ARect do
begin
  x.Left := 0;
  x.Rigth := 0;
  ...
end;

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


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

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

在某些情况下,这是一个实际问题,而在某些情况下,这不是问题(非问题案例将在问题中描述为"明智使用")。

由于可能会造成混淆,即使在可能没有混淆的情况下,一些开发人员也选择完全避免使用" with"。这似乎是教条,但是可以争论的是,随着代码的更改和增长,即使在将代码修改到一定程度以至于使" with"引起混淆的情况下," with"的使用也可能会保留,因此最好不要首先介绍其用法。


事实上:

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

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

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

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

事实上:

1
2
3
4
5
with MyRect do
begin
  Left := 0;
  Right := 0;
end;

由编译器以伪代码进行编码:

1
2
3
4
5
var aRect: ^TRect;

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

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

我个人有时会在代码中使用,但是我几乎每次生成的asm都要检查一次,以确保它能完成应有的工作。并不是每个人都有能力或有时间去做,所以恕我直言,局部变量是with的很好选择。

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

1
2
3
4
5
6
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;

使用以下命令仍然可读性强:

1
2
3
4
5
6
7
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;

甚至

1
2
3
4
5
6
7
8
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;

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

1
2
3
4
5
6
7
8
9
10
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以获取内部值。


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

基本上,有了语法,很难一眼就知道要调用哪个Left / Top / etc属性/方法。您可能有一个名为Left的局部变量和一个属性(从那时起已经有一段时间了。我已经完成了delphi,如果名称错误,对不起),它叫做Left,甚至一个叫做Left的函数。任何不熟悉ARect结构的人都可能会非常迷路。


您保存的键入内容会失去可读性。
许多调试器都不知道您指的是什么,因此调试更加困难。
它不会使程序运行更快。

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


主要是维护问题。

从语言的angular来看,WITH的想法是合理的,并且合理地使用它的理由是,保持代码的说法更合理,更小,更清晰。但是,问题在于,大多数商业代码将在其生命周期内由几个不同的人维护,而当编写时,它最初是一个小的,易于解析的构造,但是随着时间的流逝,它们很容易转变为笨重的大型结构,而WITH的范围并不大。维护人员可以轻松解析。这自然会产生错误,并且很难找到错误。

例如,说我们有一个小的函数foo,其中包含三到四行代码,这些代码被包装在WITH块中,那么实际上没有问题。但是,几年后,在几个程序员的带领下,此功能可能扩展为仍然封装在WITH中的40或50行代码。现在这很脆弱,并且已经很容易引入错误,尤其是如果维护人员开始引入额外的嵌入式WITH块的情况下。

没有其他好处-代码应该完全相同地解析并以相同的速度运行(我在D6内的3D渲染紧密循环中对此进行了一些实验,我发现没有区别)。调试器无法处理它也是一个问题-但是应该早就解决这个问题,值得一试,是否有任何好处。不幸的是没有。


我不喜欢它,因为它使调试麻烦。您不能仅通过用鼠标悬停在变量上来读取变量等的值。


您可以将其与语句结合使用,因此您最终会得到

1
2
3
4
with Object1, Object2, Object3 do
begin
  //... Confusing statements here
end

如果您认为调试器与一个人混淆,那么我看不到有人可以确定with块中发生了什么


在工作中,我们给出了从现有的Win 32代码库中删除Withs的要点,因为维护使用它们的代码需要付出额外的精力。我在以前的工作中发现了几个错误,其中一个名为BusinessComponent的局部变量被位于对象的具有相同类型的已发布属性BusinessComponent的With开头块遮盖了。编译器选择使用已发布的属性,以及使用崩溃的局部变量的代码。

我看过类似

的代码

用a,b,c,d来做(除非它们是更长的名字,在这里只是简称)
开始
i:= xyz;
结束;

试图找到xyz的来源可能是一件非常痛苦的事情。如果是c,我会尽快将其写为

i:= c.xyz;

您认为理解这很琐碎,但不是在一个800行长的函数中,该函数在一开始就使用了with!


... run faster ...

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

我认为这让我说"糟糕!"因为它很懒惰-当我阅读代码(尤其是其他人的代码)时,我喜欢看到显式的代码。因此,我什至会在Java中编写" this.field"而不是" field"。


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


只要保持简单并避免歧义,它没有任何问题。

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


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

优点经常超过缺点。

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

是的,使用with可以(缓慢地)更快地执行代码。

在下面,仅对foo进行一次评估:

1
2
3
4
5
6
with foo do
begin
  bar := 1;
  bin := x;
  box := 'abc';
end

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

1
2
3
foo.bar := 1;
foo.bin := x;
foo.box := 'abc';

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


推荐阅读

    16天的最新的内存引用:4gb上涨10元

    16天的最新的内存引用:4gb上涨10元,,内存价格上涨到4GB 10元 在今天中关村的现货市场,内存价格总体呈发展趋势,但它是小的。内存市场昨日下

    wps如何生成引用

    wps如何生成引用,WPS教程,1.wps怎么添加引用文献wps添加引用文献:1、打开文档,点击WPS文字右侧下拉菜单。2、打开插入>引用>脚注与尾注3、选

    Python之可迭代对象、迭代器、生成器

    Python之可迭代对象、迭代器、生成器,迭代,生成器,一、概念描述可迭代对象就是可以迭代的对象,我们可以通过内置的iter函数获取其迭代器,可

    应用程序对象

    应用程序对象,,应用程序对象是一个应用程序级对象,用于在所有用户之间共享信息,并且在Web应用程序运行期间可以保存数据。 应用的性质: 方法

    Java创建对象的几种方式

    Java创建对象的几种方式,对象,方法,本文目录Java创建对象的几种方式java中几种创建对象的方式1Java中创建对象的集中方式有那些JAVA创建对