Why should I not use "with" in Delphi?
Asked Answered
T

17

22

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?

Twoup answered 16/9, 2008 at 11:35 Comment(0)
S
34

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

Stedmann answered 16/9, 2008 at 11:44 Comment(0)
P
12

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.

Philbrook answered 16/9, 2008 at 11:44 Comment(7)
Agreed. This is basically the only thing I like better in VB over Delphi.Kimberly
That wouldn't work with nested "with". (Whether you like those or not.)Vachell
@Ulrich: the VB syntax does work with nested with: With fooWith .bar….Philbrook
@Konrad: Then what does .Left in the inner with mean - foo.Left or foo.bar.Left?Vachell
@Ulrich: foo.bar.Left, of course. Otherwise, nesting with would make no sense, wouldn’t it?Philbrook
Neither version makes much sense. :-) Inside the first with, you can qualify whether Left is a member of the "withed" variable or comes from the surrounding scope, by prefixing it with . or leaving it off. This distinction is gone if you nest your withs one level deeper. If the syntax were consistent .Left would mean foo.Left and ..Left would mean foo.bar.Left or something like that.Vachell
@Ulrich: well, I think that the way VB solves it is an adequate trade-off between generality and usefulness. It’s true that the concept could have been generalized further. In fact, I don’t use With any more at all. Constructors or builders with fluent interfaces offer much better readability. But I don’t think that’s the point here.Philbrook
R
12

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.

Rotifer answered 5/3, 2010 at 6:38 Comment(1)
In XE2 and newer just call: x.Create(left,top,right,bottom);. No need to use with anymore. Many standard types are implemented with the advanced record types.Myosin
M
8

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.

Mortarboard answered 16/9, 2008 at 11:53 Comment(1)
I once did some very limited testing on using with when optimising some often called code. It made no improvement to run time. If this was an issue, say for an array lookup, then a temporary variable is the solution. The compiler is just so smart, that it's usually best to leave it alone.Architectonics
T
7

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.

Twerp answered 4/9, 2012 at 16:14 Comment(0)
L
3

This debate happens in Javascript a lot too.

Basically, that With syntax makes it very hard to tell at a glance which Left/Top/etc property/method you're calling on.You could have a local variable called Left, and a property (it's been a while since I've done delphi, sorry if the name is wrong) called Left, perhaps even a function called Left. Anyone reading the code who isn't super familiar with the ARect structure could be very very lost.

Lavernlaverna answered 16/9, 2008 at 11:40 Comment(0)
M
3

What you save in typing, you lose in readability. Many debuggers won't have a clue what you're referring to either so debugging is more difficult. It doesn't make programs run faster.

Consider making the code within your with statement a method of the object that you're refering to.

Mown answered 16/9, 2008 at 11:48 Comment(0)
D
3

It's primarily a maintenance issue.

The idea of WITH makes reasonable sense from a language point of view, and the argument that it keeps code, when used sensibly, smaller and clearer has some validity. However the problem is that most commercial code will be maintained by several different people over it's lifetime, and what starts out as a small, easily parsed, construct when written can easily mutate over time into unwieldy large structures where the scope of the WITH is not easily parsed by the maintainer. This naturally tends to produce bugs, and difficult to find ones at that.

For example say we have a small function foo which contains three or four lines of code which have been wrapped inside a WITH block then there is indeed no issue. However a few years later this function may have expanded, under several programmers, into 40 or 50 lines of code still wrapped inside a WITH. This is now brittle, and is ripe for bugs to be introduced, particularly so if the maintainer stars introducing additional embedded WITH blocks.

WITH has no other benefits - code should be parsed exactly the same and run at the same speed (I did some experiments with this in D6 inside tight loops used for 3D rendering and I could find no difference). The inability of the debugger to handle it is also an issue - but one that should have been fixed a while back and would be worth ignoring if there were any benefit. Unfortunately there isn't.

Debtor answered 4/3, 2010 at 20:47 Comment(0)
I
2

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.

Intertwist answered 16/9, 2008 at 11:38 Comment(2)
That sounds like a problem with the debugger, not a problem with the language feature.Windermere
@GregHewgill You're right, it is a problem with the debugger. But it's still a perfectly sound reason to avoid its use. Furthermore, that doesn't excuse all the language feature problems with introduces. The biggest being that with actually reduces readability. NOTE: Less code (using with) does not automatically improve readability. It reduces it because it's no longer explicit what the scope of identifiers within a with block refer to. Declaring and initialising a local variable alias achieves similar reduction in code but maintains explicit dereferencing.Rudderhead
E
2

There's nothing wrong with it as long as you keep it simple and avoid ambiguities.

As far as I'm aware, it doesn't speed anything up though - it's purely syntactic sugar.

Epispastic answered 16/9, 2008 at 11:41 Comment(3)
It can speed things up if there are multiple levels of indirection involved. Consider the following: with aControl.parent.parent.parent do ...followed by multiple operations. The with statement only evaluates the triple-indirection once and uses that reference multiple times.Kimberly
How do you know the compiler doesn't do that without the with? I did some basic benchmarking a while ago, and couldn't find any difference at all.Epispastic
@MasonWheeler - The same result can be achieved by using GreatGrandParent := aControl.parent.parent.parent; and referring to GreatGrandParent's members. Only it is much more readable than with with.Chickabiddy
A
2

At work we give points for removing Withs from an existing Win 32 code base because of the extra effort needed to maintain code that uses them. I have found several bugs in a previous job where a local variable called BusinessComponent was masked by being within a With begin block for an object that a published property BusinessComponent of the same type. The compiler chose to use the published property and the code that meant to use the local variable crashed.

I have seen code like

With a,b,c,d do {except they are much longer names, just shortened here) begin i := xyz;
end;

It can be a real pain trying to locate where xyz comes from. If it was c, I'd much sooner write it as

i := c.xyz;

You think it's pretty trivial to understand this but not in a function that was 800 lines long that used a with right at the start!

Absurd answered 16/9, 2008 at 11:49 Comment(0)
S
2

You can combine with statements, so you end up with

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

And if you think that the debugger is confused by one with, I don't see how anyone can determine what is going on in the with block

Semifinal answered 27/6, 2010 at 9:37 Comment(0)
C
1

It permits incompetent or evil programmers to write unreadble code. Therefor, only use this feature if you are neither incompetent nor evil.

Commune answered 16/9, 2008 at 11:46 Comment(2)
And if you can guarantee that your code will never be read or edited by a programmer less competant than yourself.Architectonics
And if you are sufficiently competent always to use with perfectly correctly: you would simply declare a local variable alias instead, because that way the code would be explicit and far more readable in the future, at the cost of a few extra seconds of typing now.Rudderhead
C
1
... run faster ...

Not necessarily - your compiler/interpreter is generally better at optimizing code than you are.

I think it makes me say "yuck!" because it's lazy - when I'm reading code (particularly someone else's) I like to see explicit code. So I'd even write "this.field" instead of "field" in Java.

Cachalot answered 16/9, 2008 at 11:59 Comment(0)
W
1

Here is a great example why you should NEVER use with.

We were contacted by a client who claimed that our product (EurekaLog) was erasing his image in a program. In particular, it was claimed that the client's code "worked great" until EurekaLog was added to the application. After adding EurekaLog, the previously "working" code stopped working, clearing the image instead (e.g. the code's result was blank instead of expected image).

Simplified code looks really simple:

Buffer := TBitmap.Create;
try
  Buffer.SetSize(64, 64);
  Buffer.Canvas.StretchDraw(Rect(0, 0, 64, 64), Bitmap); 
  Bitmap.SetSize(64, 64);
  Bitmap.Canvas.Draw(0, 0, Buffer); 
finally
  Buffer.Free;
end;

Do you see a problem in that code?

We have found that the problem is that code accessed the already deleted TBitmapImage:

procedure TBitmap.Draw(ACanvas: TCanvas; const Rect: TRect);
// ...
begin
  with Rect, FImage do
  begin
    // ...
        StretchBlt(ACanvas.FHandle, Left, Top, Right - Left, Bottom - Top,
          Canvas.FHandle, 0, 0, FDIB.dsbm.bmWidth,
          FDIB.dsbm.bmHeight, ACanvas.CopyMode);
    // ...        
  end;
end;

Do you see a bug in the code now?

The bug is using the with operator.

Indeed, using the with operator causes the Delphi optimizer to store a reference to the evaluated expression in with somewhere. And since we are talking about an object (which are stored by reference, and not by value), it is not the object's data that is stored, but only a reference to the data. Therefore, any data change (including their deletion/clearing) from the sub-procedures called in the code will go unnoticed. In particular:

// FImage is being cached by with
with Rect, FImage do
// ...
    // FImage was deleted (recreated), but reference to the deleted object is being stored by with
    Canvas.RequiredState(csAllValid); 
// ...
      // FImage.FDIB references the deleted object
      StretchBlt(ACanvas.FHandle, Left, Top, Right - Left, Bottom - Top,
        Canvas.FHandle, 0, 0, FDIB.dsbm.bmWidth, 
        FDIB.dsbm.bmHeight, ACanvas.CopyMode);
// ...

And the worst thing about this with operator is that the debugger is not able to interpret it correctly. For example, the calculation of expressions by mouse hovering does not work. And if we try to calculate the values of the specified expressions (for example, FImage.FDIB.dsbm.bmWidth), then the debugger will, of course, return the correct value from the new FImage object, and not from the removed old one, which is preserved by the with statement.

Values of the StretchBlt's function argument were one of the things that I have checked first! But due to this "feature" of the debugger, I could not see the problem, although I had it right before my eyes. I had to go the long way.

Conclusion: our customer has found a bug in VCL. Congratulations!

Winze answered 25/8, 2023 at 10:2 Comment(0)
S
-1

We've recently banned it in our Delphi coding stnadards.

The pros were frequently outweighing the cons.

That is bugs were being introduced because of its misuse. These didn't justify the savings in time to write or execute the code.

Yes, using with can led to (mildly) faster code execution.

In the following, foo is only evaluated once:

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

But, here it is evaluated three times:

foo.bar := 1;
foo.bin := x;
foo.box := 'abc';
Spouse answered 16/9, 2008 at 11:57 Comment(7)
Are you sure about that? I would be very surprised if the compiler wasn't smart enough to optimize the second case into the first case.Epispastic
Quite sure. Delphi is the primary env. at my day job and I'm intimately familiar with it. Pascal's nature makes optimization easy due to pervasive static typing but the optimizations the compiler performs are by now technologically a few generations behind. This compiler is not as smart as we are.Bradleigh
If you store foo in a local variable, then you'll be getting the equivalent level of performance as "with".Revivalist
That's perfectly true. The performance hit is there when accessing function-type properties (function call, non-inlined pre-Delphi10) and instance variables (vtable lookup.) If you use a local and take care to place the accesses one after the other, it's as fast as the with clause.Bradleigh
If Foo is a function, or a property called by a GetFoo method, the compiler MUST execute the function each time, as it may have other side effects (generally bad design, but it does happen).Colincolinson
@GerryColl In which case you wouldn't want to be using with either.Rudderhead
The only case @Matt Lacey could be right if foo would be a method (or a method bound property), and the compiler would be mad enough to buffer its value. Now if it was the case - and I only hope it is not - what would happen if I call the bar() method (or set/get the barisnotvalid method bound property) of foo? If it would not be reevaluated, all further readed fields/properties could have fake values (because the method that has been called cahanged their value)? Or the compiler would misteriously come to reevaluate foo? How does it know if its necessary?Chickabiddy
I
-1

For Delphi 2005 is exist hard error in with-do statement - evaluate pointer is lost and repace with pointer up. There have to use a local variable, not object type directly.

Infralapsarian answered 27/7, 2010 at 9:5 Comment(1)
Can you rephrase your answer? Its very difficult to understand what you are trying to say.Pledgee

© 2022 - 2024 — McMap. All rights reserved.