Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Any Resources/Tutorials on using nested "With" statements in Delphi?

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.

like image 364
Gary Becks Avatar asked Jan 17 '12 05:01

Gary Becks


3 Answers

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.

like image 166
Nick Hodges Avatar answered Oct 20 '22 18:10

Nick Hodges


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.

like image 27
OnTheFly Avatar answered Oct 20 '22 17:10

OnTheFly


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.

like image 39
NGLN Avatar answered Oct 20 '22 17:10

NGLN