Why should I not use “with” in Delphi?

2019-01-04 13:55发布

I've heard many programmers, particularly Delphi programmers scorn the use of 'with'.

I thought it made programs run faster (only one reference to parent object) and that it was easier to read the code if used sensibly (less than a dozen lines of code and no nesting).

Here's an example:

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

I like using with. What's wrong with me?

11条回答
混吃等死
2楼-- · 2019-01-04 14:47

I do not like it because it makes debbuging a hassle. You cannot read the value of a variable or the like by just hovering over it with a mouse.

查看更多
beautiful°
3楼-- · 2019-01-04 14:49

I prefer the VB syntax in this case because here, you need to prefix the members inside the with block with a . to avoid ambiguities:

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

But really, there's nothing wrong with with in general.

查看更多
一夜七次
4楼-- · 2019-01-04 14:52

It is not likely that "with" would make the code run faster, it is more likely that the compiler would compile it to the same executable code.

The main reason people don't like "with" is that it can introduce confusion about namespace scope and precedence.

There are cases when this is a real issue, and cases when this is a non-issue (non-issue cases would be as described in the question as "used sensibly").

Because of the possible confusion, some developers choose to refrain from using "with" completely, even in cases where there may not be such confusion. This may seem dogmatic, however it can be argued that as code changes and grows, the use of "with" may remain even after code has been modified to an extent that would make the "with" confusing, and thus it is best not to introduce its use in the first place.

查看更多
够拽才男人
5楼-- · 2019-01-04 14:54

In fact:

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

and

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

Will generate exactly the same assembler code.

The performance penalty can exist if the value of the with clause is a function or a method. In this case, if you want to have good maintenance AND good speed, just do what the compiler does behind the scene, i.e. create a temporary variable.

In fact:

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

is encoded in pseudo-code as such by the compiler:

var aRect: ^TRect;

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

Then aRect can be just a CPU register, but can also be a true temporary variable on stack. Of course, I use pointers here since TRect is a record. It is more direct for objects, since they already are pointers.

Personally, I used with sometimes in my code, but I almost check every time the asm generated to ensure that it does what it should. Not everyone is able or has the time to do it, so IMHO a local variable is a good alternative to with.

I really do not like such code:

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;

It is still pretty readable with with:

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;

or even

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;

but if the inner loop is huge, a local variable does make sense:

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;

This code won't be slower than with: compiler does it in fact behind the scene!

By the way, it will allow easier debugging: you can put a breakpoint, then point your mouse on Obj or Nested directly to get the internal values.

查看更多
Luminary・发光体
6楼-- · 2019-01-04 14:57

One annoyance with using with is that the debugger can't handle it. So it makes debugging more difficult.

A bigger problem is that it is less easy to read the code. Especially if the with statement is a bit longer.

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

Which Form's CallThisFunction will be called? Self (TMyForm) or OtherForm? You can't know without checking if OtherForm has a CallThisFunction method.

And the biggest problem is that you can make bugs easy without even knowing it. What if both TMyForm and OtherForm have a CallThisFunction, but it's private. You might expect/want the OtherForm.CallThisFunction to be called, but it really is not. The compiler would have warned you if you didn't use the with, but now it doesn't.

Using multiple objects in the with multiplies the problems. See http://blog.marcocantu.com/blog/with_harmful.html

查看更多
登录 后发表回答