6

I am trying to come to grips with using with statements in delphi properly.

Overall it seems fairly simple to do simple things with but I am interested in finding some good code examples and/or tutorials on using nested with statements. E.G.

with object1, object2, etc... do 
  begin
  statements
  end;

What I am unsure of is the order of precedence when using with statements in such a manner.

Any advice is appreciated.

Lars Truijens
  • 42,837
  • 6
  • 126
  • 143
Gary Becks
  • 887
  • 2
  • 13
  • 21

5 Answers5

23

The best advice I can give you is:

Do not use with ever.

If you feel like using 'with', go and lie down until the feeling passes.

If you feel like using a nested with, pound your hand with a hammer until the desire goes away.

'with' is just a bug waiting to happen. Altering classes used with it can alter the meaning of your code. It creates imprecise semantics, and that is always bad.

Saving keystrokes is never a good reason to use 'with'. A few more keystrokes now will save you a lot of pain later.

'With' should be shunned.

Nick Hodges
  • 16,902
  • 11
  • 68
  • 130
  • 5
    @The_Fox Final word on that goes to Verity Stob: http://www.theregister.co.uk/2012/01/16/verity_stob_sons_of_khan_2011/print.html – David Heffernan Jan 17 '12 at 07:48
  • 5
    I largely agree with this advice, although I would probably be inclined to be less dogmatic. I think one can judge each individual candidate for use of `with` on its own merits, thus avoiding dogma, and still find that the judgement comes out against using `with`! It's a shame that you did not address the main question asked though which concerned precedence of the dreaded nested `with`. Now, rejection of nested `with` is probably a rule I would accept with feeling the need to use the word dogma! ;-) – David Heffernan Jan 17 '12 at 07:52
  • If you never use with you don't ever make a mistake using it, and you don't waste time considering whether to use it or not. I'm not sure why "dogma" in programming is a bad thing. Good, solid rules that are followed consistently is the hallmark of clean code. Why such rules are derided as "dogma" is a mystery to me. – Nick Hodges Jan 17 '12 at 13:45
  • With is evil. We thought about a check-in rule that reject source code that contains with as a command in it. – Andreas Richter Jan 17 '12 at 14:33
  • @NickHodges I always like to question everything I do. Hard rules like don't use `with` and don't use `goto` grate with me. And in fact I do never use either as it happens! – David Heffernan Jan 17 '12 at 16:21
  • 2
    (I'll play the heretic role here) Delphi's `with` need an modification on it's syntax. The best starting point (arggggghhhh) is the Vb.Net `with`. All the goods and none of the problems of Delphi `with`. – Fabricio Araujo Jan 17 '12 at 17:34
  • And yes, avoid `with` like the plague... ;-) – Fabricio Araujo Jan 17 '12 at 17:34
  • 2
    I have been bitten by 'with' once - never again. In a complex contructor, it appeared that my code was unable to simply increment an integer. After hours of fiddling and embarrassing myself by posting my problem on a group, 10 or so posters replies 'with!'. Sure enough, some obscure property had the same name as my integer... – Martin James Jan 17 '12 at 17:39
  • Well I guess that settles it. – Gary Becks Jan 17 '12 at 17:58
  • @David -- hey, that's fine, but why use the word "Dogma" as a weapon? Surely there has to be some programming rules you follow.... – Nick Hodges Jan 17 '12 at 18:35
  • @NickHodges Weapon is a bit strong. Sure I follow rules and guidelines. But I think they should never be absolute. One should always be prepared to break the rules if it turns out to be a better compromise to do so. Interestingly all the arguments you make regarding `with` apply also to `uses`. But we are stuck with that one. I can remember using `FROM` in Modula-2 but I'm not sure that was much better. We really need decent namespaces in OP. P.S. Did you enjoy reading Verity's latest instalment of the Sons of Kahn. – David Heffernan Jan 17 '12 at 19:10
  • @David, out of curiousity: Could you give an example use of `with` you find acceptable or even useful? I sometimes use it like this: `with Somehow.Get.A.TRect do Canvas.Ellipse(Left, Top, Right, Bottom);`. – Uli Gerhardt Jan 18 '12 at 07:39
  • Aargh - bad example: TCanvas.Ellipse has an overload accepting a TRect. But just pretend it doesn't to get the point. :-) – Uli Gerhardt Jan 18 '12 at 07:41
  • 1
    @Ulrich Little danger in with TMyForm.Create(nil) do as the entire contents of a method with no local variables. But personally I always use a local variable to hold the form reference. My point is not that you should use with but that you should remain open minded. – David Heffernan Jan 18 '12 at 07:46
  • 5
    I have no problem with dogmatic programming rules, as long as there is guidance provided on an alternative approach. Unfortunately, a lot of instances I see that start with the words "Never ever..." seem to rarely conclude with the words, "... and this is how you should do it....". The alternative to "with" is, of course, to declare a local variable, assign the value of the local variable to the long-winded-object-name and then call methods on the local variable. e.g. var tbl : TDataset; tbl := tblMyReallyLongNamedTable; tbl.first; while not tbl.eof; etc etc. – Stuart Jan 18 '12 at 10:04
  • @UliGerhardt: "Aargh - bad example ... But just pretend it doesn't tp get the point". Aah but that is *exactly* the point as to why **with** is so bad. ;-) – Disillusioned Jun 11 '12 at 06:15
7

An excerpt from the online reference states:

When multiple objects or records appear after with, the entire statement is treated like a series of nested with statements. Thus:

with obj1, obj2, ..., objn do statement

is equivalent to:

 with obj1 do
  with obj2 do
    ...
    with objn do
      // statement

In this case, each variable reference or method name in statement is interpreted, if possible, as a member of objn; otherwise it is interpreted, if possible, as a member of objn1; and so forth. The same rule applies to interpreting the objs themselves, so that, for instance, if objn is a member of both obj1 and obj2, it is interpreted as obj2.objn.

OnTheFly
  • 2,059
  • 5
  • 26
  • 61
  • 14
    Meanwhile, excessive use of `with` (and sometimes **any** use of `with` even) being frowned upon due its impact on the code readability and debugging inconveniences. – OnTheFly Jan 17 '12 at 05:35
  • @TLama, you vandalized posts of other users for whatever petty revenge purposes. Does your profile indicates how often you do so, besides this recorded case? And of course only you have public benefit :-) I do not have a time to review your posts right now, however, you might not be so pleased by the outcome of such invitation (judging from your answers i've seen already). – OnTheFly Mar 13 '12 at 08:17
  • @TLama, [evidence record](http://stackoverflow.com/revisions/8890131/3). Please read my remark about **time** above. – OnTheFly Mar 13 '12 at 08:34
  • @TLama, tell me why i should trust you. How "good" are your answers are excellent example why. Of course i'm stronger, you are camping in my profile. – OnTheFly Mar 13 '12 at 09:05
  • @TLama, or what? You'll make laughingstock of yourself? Your trolling attracted my attention to your posts, i reviewed them and... rated. Do you realize how much of precious time i've spent on this page with your whinings already? I think you just want more attention and idle arguments elsewhere... – OnTheFly Mar 13 '12 at 09:55
  • @TLama, i only can say what your behaviour is very wrong. Now waste my time no more, please. – OnTheFly Mar 13 '12 at 10:17
7

Now that there are enough opinions given, I will try to fully answer your question, though this previous answer answers the syntax part of your question pretty well already. Its reference to the documentation explains the order of precedence, as well as other interesting rules about with. Consider it as a mandatory read.

As you may have understood already, the only problem with (an unnested) with is that instead of addressing a specific instance member, you could be addressing a member of Self (or of an instance from one nested level up) with the same name, which you obviously did not intend to:

procedure TForm1.SomeRoutine;
var
  Button: TControl;
begin
  Button := Button1;
  with Button do
    Caption := 'Press here';
end;

Run, and be surprised the form's caption is changed. Sure, TControl declares a Caption property, but that one is protected, thus Caption in this code refers to that of the form. But also the next example does not guarantee setting the caption of the button:

procedure TForm1.SomeRoutine;
begin
  with Button1 do
    Caption := 'Press here';
end;

... because maybe Button1 is declared as some exotic button type which has a caption, but the property name could be Title, Beschriftung, or Legende.

These are simple examples which are easily fixed and almost need no debugging. But consider non-visual members of in-memory records and objects: those mistakes are very hard to debug, because: where to look? Sure, with a little luck, Self does not have such member, in case the compiler will warn:

procedure TForm1.SomeRoutine;
begin
  with Button1 do
    Cpation := 'Press here';
end;

... but do not expect to only make errors which are caught by the compiler.

These kind of mistakes are made very easily. By me, by you, and by any other (experienced) developer. Especially when you work on or work with code which is under construction or is being refactored. And obviously, with nested withs, the debugging problems from these mistakes are piling up to exponential proportions.

Now, unlike the rest of us here, I use with quite often, but only on fixed library types which' members are never to become renamed. E.g.: TRect, TPoint, TMessage, TGridRect, TControl, TCanvas, etc... I.e. on almost all RTL record types as well as some VCL classes, but almost never on my own library types. If you really wánt to use with, I suggest the following:

  • Safe example:

    with ARect do
      ExcludeClipRect(DC, Left, Top, Right, Bottom);  //TRect will always have these
    
  • Tricky example:

    with Query, Parameters do
    begin
      Connection := AConnection;
      ParamValues['ID'] := ID; //TParameters has a few same named members as dataset
      Open;
    end;
    
  • Bad example:

    with TMyLabel do
      Color := clYellow; //My label may become transparent in future
    

Edit:

I have become a member of the no-with-camp.

My examples above assume danger with with only in upward direction, like mixing up Child.Caption with Self.Caption. But recently, when porting some code from D7 to XE2, I experienced trouble in downward direction. Actually, using the 'safe example' from above, sure enough:

  with GripRect[I] do
  begin
    ...
    Left := Width - W[I];

Intending Width to be that of Self, but since XE2's TRect has also a Width member, I had to rewrite the code to:

    Left := Self.Width - W[I];

Conclusion: There really is no safe usuage of with.

Community
  • 1
  • 1
NGLN
  • 43,011
  • 8
  • 105
  • 200
  • 3
    Wow, last night I dreamed about getting 800 downvotes on this answer... ;) – NGLN Jan 22 '12 at 10:54
  • +1 for no safe usuage of `with`. I'm personally using it just when I need to instantiate object without keeping its reference (like `with TEdit.Create(Self) do` and even there might be unsafe... – TLama Jan 23 '13 at 22:53
3

Delphi's with is problematic - it's "imprecise semantics" really can bite you on the rear. Nested with... OMG... Is a disaster waiting to happen. Never needed the nested version on 13 years of Delphi. And I'll avoid for the next years.

EDIT: In other discusions about with, some pointed that a new syntax would be nice. An starting point is Vb.Net with:

With Something.Somewhere
  .over.the.rainbow = True
End With

I dislike (a matter of taste mine) a little, but it's much better than Delphi's. An suggestion of alternative syntax which I saw on those discussions is:

With SS:Something.Somewhere.over do
  SS.the.rainbow = true;
  SS.the.Storm = false;
end;
Fabricio Araujo
  • 3,810
  • 3
  • 28
  • 43
3

I hate to even say this, but it's not exactly hard to figure out how scope rules work. Last scope in wins. Always.

But human beings have this problem, which in psychology is called Chunk Theory. When you are presented with seven things to keep track of in your mind, one falls out, because your brain has about 6 local registers to keep things in. Imagine the simple identifier Foo appears here:

   with EXPRESSION1 do begin
     with EXPRESSION2 do begin
               Foo;
     end;
   end;

The above is for all intents identical to with EXPRESSION1, EXPRESSION2 do begin....

So let's see how simple rules like scope get too complicated to follow after a while. Let's imagine that we have the following levels of scope in some delphi application we're working in:

  • The unit scope of every unit that is in your use clauses.
  • Your own unit's implementation section scope (Unit-internals)
  • The class scope (identifiers inside your current class if you are writing a method)
  • The local procedure or method scope (var section in a procedure or function).
  • The first with statement in the sample above.
  • The second with statement in the sample above.

Update David has pointed out to me that I should mention that instead of 6 "scopes" above, we really have 5+x scopes, where x is the length of both your uses clauses.

Now, the problem isn't the WITH statement being unclear, it's that human beings can easily get lost when you have 6 layers of lexical scoping going on, as above, and requires you not only to be aware of all places where Foo is defined, because if you thought that the innermost With statement has something named Foo defined, and it doesn't you get a rather nasty and hard to find bug in your code. And so, we have very smart, very capable people like Nick, saying very sensible things like "never use with". And I agree about 99% with Nick.

I also think that you could say that the enemy is not with, it's the tendency Delphi developers have to just keep growing their applications iteratively in a "RAD style" until they have become monstrosities. A unit which uses 200 other units is a mess, each of which uses 200 other units, is going to cause you more grief even than rampant abuse of WITH statements.

WITH is not the entire problem in bad code, it's just the most common bee in a Delphi developer's bonnet. Maybe it gets more attention than over-large uses-clauses, but over my entire career, dependency-hell and enormous-uses-clauses have done 100 times more to make life hard, than WITH. So I think that WITH considered harmful is over-done, and other things which ought to be considered more, are under considered.

A reasonable alternative to using with, is to use a single letter variable name (I know, go ahead and disagree), and avoid all scope ambiguity:

procedure NoWithPlease;
var
   a:TSomething;
begin
   a :=  Some.Long.Expression[x].That.You.Dont.Want.To.Repeat.Six.Times;
   a.Foo := 'test';
   a.Bar := 'test2'
end;

That is better, many would say, than this:

procedure WithPleasure;
begin
   with Some.Long.Expression[x].That.You.Dont.Want.To.Repeat.Six.Times do begin
     Foo := 'test';
     Bar := 'test2'
   end;
end;

Now I've been bitten by WITH too many times to advocate its unrestricted use. But I also think there are times when it's actually better than doing the local variable stuff. It doesn't require that you declare a local variable, or determine the type of the expression. If delphi had type inference (the auto keyword in C++), then I would say, we could easily do without WITH completely, but as it is, it is kind of a way to avoid statically creating a dependency on implementation types, and along with the ability to create readable sub-scopes, it can sometimes make your code easier to read, and sometimes make it worse, especially when there is more than one level of WITH statements.

However, opinions on that matter vary, and like other programming topics, tends to turn into a bike-shed discussion, or worse yet, a holy war.

My suggested rules:

  1. Avoid WITH unless it makes your code better. Most places that you think you need a with, you don't, use a local variable instead.

  2. Always avoid multi-level WITH statements.

Warren P
  • 65,725
  • 40
  • 181
  • 316
  • C++ auto is not duck typing. That is statically typed, just like C# var. ItLs type inference. As for your post, you didn't mention uses which bring new names into scope and to understand what names are brought in you need to read every single file in your uses list. – David Heffernan Jan 18 '12 at 07:30
  • Good point David, I'll edit and improve my answer. It was sloppy to say "duck typing" since as you say, it's compile time inference. But my intent was to communicate that leaving "x:TSomething" local variable declarations around your code creates a hard-coded dependency on an expression's type name. – Warren P Jan 18 '12 at 14:16
  • 3
    If you have Some.Long.Expression[x].That.You.Dont.Want.To.Repeat.Six.Times then you should refactor by applying the Law of Demeter, and then you won't "need" the with. Feeling the need for "with" is a code smell. – Nick Hodges Jan 18 '12 at 21:58
  • 1
    Sometimes the thing that you want to refactor is the VCL or a massive third party library like Developer Express components. What would you do then, Nick? – Warren P Jan 19 '12 at 15:32
  • Maybe class helpers would help. (`Some.ClassHelper` instead of `Some.Long...`). The more I think about it, the more I think you're right, Nick. – Warren P Jan 19 '12 at 16:18
  • 1
    @NickHodges: the actual status of `with` is disastrious, but the idea behind it not... I really miss a kind of `with` in C#. Delphi's one just need an overhaul in the syntax. – Fabricio Araujo Jan 23 '12 at 18:06