问题
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.
回答1:
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.
回答2:
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
.
回答3:
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
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
.
回答4:
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;
回答5:
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:
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.Always avoid multi-level
WITH
statements.
来源:https://stackoverflow.com/questions/8889911/any-resources-tutorials-on-using-nested-with-statements-in-delphi