Why should I not use "with" in Delphi?
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?
Solution 1:
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
Solution 2:
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.
Solution 3:
It would be great if the with
statement would be extented the following way:
with x := ARect do
begin
x.Left := 0;
x.Rigth := 0;
...
end;
You wouldn't need to declare a variable 'x'. It will be created by the compiler. It's quick to write and no confusion, which function is used.
Solution 4:
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.
Solution 5:
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.