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

我听说过很多程序员,尤其是Delphi程序员不屑于使用'with'。

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

这是一个例子:

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

我喜欢用。 我怎么了?

一个恼人的使用是debugging器不能处理它。 所以它使debugging更加困难。

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

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

哪个窗体的CallThisFunction将被调用? 自我(TMyForm)或其他表单? 如果不检查OtherForm是否具有CallThisFunction方法,则无法知道。

而最大的问题是,即使不知道它,也可以使错误变得容易。 如果TMyForm和OtherForm都有一个CallThisFunction,但它是私有的。 你可能期望/想要调用OtherForm.CallThisFunction,但实际上并不是这样。 如果你不使用with,编译器会提醒你,但现在不会。

使用与多个对象乘以问题。 请参阅http://blog.marcocantu.com/blog/with_harmful.html

在这种情况下,我更喜欢VB语法,因为在这里,你需要在with块的前面加上a . 避免含糊不清:

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

但是真的,一般来说没有什么问题。

如果with下述方式扩大with声明,那将是非常好的:

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

你不需要声明一个variables“x”。 它将由编译器创build。 编写起来很快,而且没有混淆,使用哪个函数。

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

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

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

由于可能的混淆,一些开发者select不使用“完全”,即使在没有这种混淆的情况下也是如此。 这可能看起来是教条式的,然而可以认为,随着代码的变化和增长,即使在代码被修改到可能使“with”混淆的程度之后,“with”的使用也可能保持不变,因此最好不要首先介绍它的用途。

事实上:

 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子句的值是一个函数或一个方法,性能可能会下降。 在这种情况下,如果要保持良好的维护和良好的速度,只需执行编译器在场景后面执行的操作,即创build一个临时variables

事实上:

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

由编译器以伪代码编码:

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

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

就我个人而言,我有时在我的代码中使用,但我几乎每次检查生成的asm,以确保它应该做什么。 不是每个人都能够或有时间做到这一点,所以恕我直言, 本地variables是一个很好的select。

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

 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; 

但是如果内部循环是巨大的,局部variables确实是有意义的:

 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; 

这段代码不会慢于:编译器事实上是在幕后!

顺便说一句,它将允许更简单的debugging:您可以放置​​一个断点,然后将鼠标指向Obj或直接Nested来获取内部值。

这个争论也发生在Javascipt。

基本上,使用语法使得很难一目了然地告诉你正在调用哪个Left / Top / etc属性/方法。你可以有一个名为Left的局部variables和一个属性(我已经有一段时间了做delphi,不好意思,如果名称是错误的)称为左,甚至可能是一个名为左function。 任何读取不熟悉ARect结构的代码都可能会非常丢失。

在打字时你节省了你的可读性。 许多debugging器都不知道你所指的是什么,所以debugging比较困难。 它不会使程序运行得更快。

考虑在你的with语句中使用你引用的对象的方法。

这主要是一个维护问题。

从语言的angular度来看,WITH的思想是合理的,当它合理使用时,代码保持小而清晰的说法是有效的。 然而,问题在于,大多数商业代码将由一些不同的人维护,而且在开始时,作为一个小的,易于parsing的构造,在编写时可以很容易地随着时间的推移变成难以处理的大型结构,其中WITH的范围不是很容易被维护人员parsing。 这自然会产生错误,而且很难find。

例如,假设我们有一个小函数foo,其中包含三行或四行代码,这些代码已经被封装在一个WITH块中,那么确实没有问题。 然而几年之后,这个函数可能已经在几个程序员的基础上扩展成40或50行代码,这些代码仍然包含在一个WITH中。 这个现在变得很脆弱,并且已经成熟了,尤其是如果维护人员介绍了更多的embedded的WITH块的话。

WITH没有其他的好处 – 代码应该被parsing完全一样,并以相同的速度运行(我在D6内部用于3D渲染的紧密循环中做了一些实验,我可以find没有区别)。 debugging器无法处理它也是一个问题 – 但是应该早一点修复,如果有任何好处,值得忽略。 不幸的是没有。

我不喜欢它,因为它使讨厌的麻烦。 只要用鼠标hover在上面,就无法读取variables的值。

在工作中,我们给出了从现有Win 32代码库中删除Withs的要点,因为需要额外的努力来维护使用它们的代码。 我在之前的一个工作中发现了一些错误,其中一个名为BusinessComponent的局部variables被一个与一个已发布属性BusinessComponent相同types的对象的开始块所掩盖。 编译器select使用发布的属性和代码来使用本地variables崩溃。

我看过类似的代码

与a,b,c,d做{除了他们是更长的名字,在这里缩短)开始i:= xyz;
结束;

试图findxyz的来源可能是一个真正的痛苦。 如果是c,我会更早写下来

我:= c.xyz;

你认为这是相当简单的了解这一点,但不是在800线长的function,在一开始就使用了正确的!

你可以结合语句,所以你最终

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

如果你认为debugging器被一个人搞糊涂了,我不知道有人怎么知道在这个块上发生了什么

只要保持简单并避免含糊不清,就没有任何问题。

据我所知,它并没有加快速度 – 这纯粹是句法糖。

它允许无能或邪恶的程序员编写不可读的代码。 因此,只有使用这个function,如果你既无能也不邪恶。

… 跑得更快 …

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

我想这让我说“哎呀!” 因为它是懒惰的 – 当我正在阅读代码(特别是其他人)时,我喜欢看到明确的代码。 所以我甚至会在Java中写“this.field”而不是“field”。

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

专业人士经常超过这个缺点。

这是由于滥用而引入的错误。 这些都没有理由能够节省时间来编写或执行代码。

是的,使用可以导致(温和)更快的代码执行。

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

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

但是,这里评估三次:

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

对于Delphi 2005而言,在待办事宜陈述中存在硬性错误 – 评估指针丢失,并且用指针向上移动。 必须使用局部variables,而不是直接使用对象types。