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

折月煮酒 提交于 2019-12-14 00:51:45

问题


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 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.




回答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 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.




回答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:

  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.



来源:https://stackoverflow.com/questions/8889911/any-resources-tutorials-on-using-nested-with-statements-in-delphi

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!