我听过很多程序员,特别是Delphi程序员嘲笑使用'with'.
我认为它使程序运行得更快(只有一个对父对象的引用),并且如果使用得当,它更容易阅读代码(少于十几行代码并且没有嵌套).
这是一个例子:
procedure TBitmap32.FillRectS(const ARect: TRect; Value: TColor32); begin with ARect do FillRectS(Left, Top, Right, Bottom, Value); end;
我喜欢用with
.我怎么了?
使用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
在这种情况下我更喜欢VB语法,因为在这里,你需要在with块内的成员前加一个.
以避免含糊不清:
With obj .Left = 10 .Submit() End With
但实际上,with
一般来说没有任何问题.
如果with
通过以下方式扩展声明将会很棒:
with x := ARect do begin x.Left := 0; x.Rigth := 0; ... end;
您不需要声明变量'x'.它将由编译器创建.它写得快,没有混淆,使用了哪个功能.
"with"不太可能使代码运行得更快,编译器更有可能将其编译为相同的可执行代码.
人们不喜欢"with"的主要原因是它可能会引入关于命名空间范围和优先级的混淆.
有些情况下,这是一个真正的问题,以及这是一个非问题的情况(非问题情况将在问题中描述为"明智地使用").
由于可能存在混淆,一些开发人员选择完全避免使用"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
子句的值是函数或方法,则可能存在性能损失.在这种情况下,如果你想要有良好的维护和良好的速度,只需执行编译器在场景后面做的事情,即创建一个临时变量.
事实上:
with MyRect do begin Left := 0; Right := 0; end;
由编译器以伪代码编码:
var aRect: ^TRect; aRect := @MyRect; aRect^.Left := 0; aRect^.Right := 0;
然后aRect
可以只是一个CPU寄存器,但也可以是堆栈上的真正临时变量.当然,我在这里使用指针,因为TRect
是record
.它对于对象更直接,因为它们已经是指针.
就个人而言,我有时会在我的代码中使用,但我几乎每次都会检查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
:编译器确实在场景后面做了!
顺便说一句,它将允许更容易的调试:你可以放置一个断点,然后指向鼠标Obj
或Nested
直接获取内部值.