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.
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.
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 ofobjn1
; and so forth. The same rule applies to interpreting the objs themselves, so that, for instance, ifobjn
is a member of bothobj1
andobj2
, it is interpreted asobj2.objn
.
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 with
s, 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
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With