有关在Delphi中使用嵌套“With”语句的任何资源/教程吗?

时间:2012-01-17 05:05:08

标签: delphi with-statement

我正在尝试正确使用delphi中的语句。

总的来说,做简单的事情看起来相当简单,但我有兴趣找到一些关于使用嵌套语句的好的代码示例和/或教程。 E.G。

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

我不确定的是以这种方式使用语句时的优先顺序。

感谢任何建议。

5 个答案:

答案 0 :(得分:20)

我能给你的最好建议是:

不要永远使用。

如果你想使用'with',那就去躺下,直到感觉过去。

如果您想使用嵌套,请用锤子敲击您的手,直到欲望消失为止。

'with'只是一个等待发生的错误。更改使用它的类可以改变代码的含义。它创建了不精确的语义,这总是很糟糕。

保存击键绝不是使用'with'的好理由。现在再进行一些击键可以为您节省很多痛苦。

'有'应该避开。

答案 1 :(得分:7)

来自online reference州的摘录:

  

当多个对象或记录出现后,整个   语句被视为一系列嵌套的语句。因此:

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

相当于:

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

在这种情况下,语句中的每个变量引用或方法名称都是   如果可能,解释为objn的成员;否则就是   如果可能,解释为objn1的成员;等等。相同   规则适用于解释objs本身,因此,为   例如,如果objnobj1obj2的成员,则会对其进行解释   为obj2.objn

答案 2 :(得分:7)

既然已经有足够的意见,我会尝试完全回答你的问题,尽管this previous answer已经很好地回答了你问题的语法部分。它对documentation的引用解释了优先顺序,以及其他有趣的规则。将其视为强制性阅读。

正如您可能已经理解的那样,(一个未通过的)with唯一问题是,您可能正在寻找{{1}的成员,而不是寻址特定的实例成员(或者来自一个嵌套级别的实例)具有相同的名称,您显然不打算这样做:

Self

运行,并惊讶于表单的标题已更改。当然,procedure TForm1.SomeRoutine; var Button: TControl; begin Button := Button1; with Button do Caption := 'Press here'; end; 声明了TControl属性,但该属性受到保护,因此此代码中的Caption指的是表单的Caption。但是下一个例子也不能保证设置按钮的标题:

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

...因为可能Button1被声明为具有标题的异常按钮类型,但属性名称可以是TitleBeschriftung,或Legende

这些是很容易修复的简单示例,几乎不需要调试。但是考虑内存中记录和对象的非可视成员:这些错误很难调试,因为:在哪里看?当然,运气不错,Self没有这样的成员,以防编译器发出警告:

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

...但不要指望只会产生编译器捕获的错误。

这些错误很容易 。由我,由您和任何其他(经验丰富的)开发人员。特别是当您处理或使用正在构建或正在重构的代码时。显然,对于嵌套的with s,这些错误导致的调试问题正在堆积成指数级别。

现在,与我们其他人不同,我经常使用with,但仅限于“成员永远不会重命名的固定库类型。例如:TRect, TPoint, TMessage, TGridRect, TControl, TCanvas,等... I.e.几乎所有RTL记录类型以及一些VCL类,但几乎从不在我自己的库类型上。如果你真的不想使用with,我建议如下:

  • 安全示例:

    with ARect do
      ExcludeClipRect(DC, Left, Top, Right, Bottom);  //TRect will always have these
    
  • 棘手的例子:

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

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

编辑:

我已成为无营地的成员。

上面的例子假设with仅在向上方向存在危险,例如将Child.Caption与Self.Caption混淆。但是最近,当将一些代码从D7移植到XE2时,我在向下方向遇到了麻烦。实际上,使用上面的“安全示例”,当然可以:

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

Width视为Self,但由于XE2的TRect也有Width成员,我必须将代码重写为:

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

结论:with确实没有安全用法。

答案 3 :(得分:3)

Delphi的with是有问题的 - 它是“不精确的语义”真的可以咬你的后方。 嵌套with ... OMG ......灾难等待发生。从来不需要嵌套版本 13年的德尔福。我会在接下来的几年里避免。

编辑: 在关于with的其他讨论中,有人指出新语法会很好。一个起点 是Vb.Net with

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

我不喜欢(有点味道),但它比Delphi好多了。我在这些讨论中看到的替代语法的建议是:

With SS:Something.Somewhere.over do
  SS.the.rainbow = true;
  SS.the.Storm = false;
end;

答案 4 :(得分:2)

我讨厌甚至这样说,但要弄清楚范围规则是如何运作的并不是很难。胜利的最后范围。总是

但是人类有这个问题,在心理学上称之为块理论。当你被提出七个要记住的东西时,一个就会失败,因为你的大脑有大约6个本地寄存器来保存。想象一下,简单的标识符Foo出现在这里:

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

上述内容适用于与with EXPRESSION1, EXPRESSION2 do begin...相同的所有意图。

让我们看看范围之类的简单规则在一段时间后变得过于复杂。让我们假设我们在一些delphi应用程序中具有以下级别的范围:

  • use子句中每个单元的单位范围。
  • 您自己单位的实施部分范围(单位内部)
  • 类范围(如果您正在编写方法,则是当前类中的标识符)
  • 本地过程或方法范围(过程或函数中的var部分)。
  • 上面示例中的第一个with语句。
  • 上面示例中的第二个声明。

更新 David向我指出,我应该提到的是,我们确实有5 + x范围,而不是上面的6个“范围”,其中x是{{1}的长度条款。

现在,问题不在于WITH语句不清楚,如上所述,当你有6层词汇范围时,人类很容易迷失,并要求你不仅要知道所有的地方定义了uses,因为如果你认为最里面的With语句定义了一个名为Foo的东西,那么你就不会在代码中找到一个相当讨厌且难以找到的bug。所以,我们有非常聪明,非常有能力的人,比如尼克,说非常明智的事情,比如“永远不要用”。我同意尼克的99%。

我也认为你可以说敌人没有,这是Delphi开发人员必须继续以“RAD风格”迭代地增加他们的应用程序,直到他们成为怪物。使用200个其他单位的单位乱七八糟,每个单位使用200个其他单位,甚至比滥用Foo声明更容易让你感到悲伤。

WITH不是坏代码中的全部问题,它只是Delphi开发人员发动机罩中最常见的蜜蜂。也许它比过度使用 - 条款更受关注,但在我的整个职业生涯中,依赖性 - 地狱和巨大用途 - 条款使生活变得比WITH多100倍。所以我认为WITH已经过度完成,其他应该考虑更多的事情正在考虑之中。

使用with的合理替代方法是使用单个字母变量名称(我知道,继续并且不同意),并避免所有范围模糊:

WITH considered harmful

许多人会说,这比这更好:

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;

现在我被procedure WithPleasure; begin with Some.Long.Expression[x].That.You.Dont.Want.To.Repeat.Six.Times do begin Foo := 'test'; Bar := 'test2' end; end; 多次叮咬,以提倡其不受限制的使用。但我也认为有时它实际上比做局部变量更好。它不要求您声明局部变量,也不需要确定表达式的类型。如果delphi有WITH(C ++中的type inference关键字),那么我会说,我们可以很容易地完全没有auto,但事实上,它是一种避免的方式静态地创建对实现类型的依赖,以及创建可读子范围的能力,它有时可以使您的代码更容易阅读,有时会使其更糟,特别是当有WITH级以上时声明。

然而,关于这个问题的意见各不相同,并且像其他编程主题一样,往往会变成自行车讨论,或更糟糕的是,一场圣战。

我建议的规则:

  1. 避免WITH,除非它使您的代码更好。大多数你认为需要的地方,你不需要使用局部变量。

  2. 始终避免多级WITH语句。