The Wiert Corner – irregular stream of stuff

Jeroen W. Pluimers on .NET, C#, Delphi, databases, and personal interests

  • My badges

  • Twitter Updates

  • My Flickr Stream

  • Pages

  • All categories

  • Enter your email address to subscribe to this blog and receive notifications of new posts by email.

    Join 1,437 other followers

Delphi: `with` dos and dont’s (more of the latter though).

Posted by jpluimers on 2014/02/18

About a year ago, I wrote about Delphi: you should avoid the with statement as it makes your code less future proof. Then I already tweeted I would follow up. Time to do it now (:

Besides my first post, these links inspired me most:

Posts about the with statement usually cause a stir: people either like or dislike it with passion.

Starting with some history and examples, this posts lists a few DOs and DON’Ts when using the with statement, shows advantages and drawbacks, and shows you tools to eliminate with statements.

Then

No wonder people are passionate about the with statement: it has been a feature ever present in Pascal. It was introduced with reason, but it also has drawbacks. Though briefly mentioned in the 1983 “Standard Pascal User Reference Manual” Paperback by Doug Cooper (Look Inside the Standard Pascal User Reference Manual: Doug Cooper: 9780393301212: Amazon.com: Books, then search for “with”) it was a very important addition at that time: 1970s and 1980s.

Remember Turbo Pascal 1.0 in 1983 being one of the first products having an IDE, but not even a debugger?

Basically back then, your best editor could do almost nothing so anything that saved typing lots of text was a big plus.

And with would potentially save a lot of text.

“Dull” text that replaced the full access path of relatively simple situations: record, or maybe nested records.

Back then you had very simple scope: records only held storage. Methods were relatively straight forward global functions or procedures.

Now

Fast forward to now:

  • classes, namespaces, units that make scoping a truckload more complex
  • excellent IDEs available for almost any programming language. Syntax highlighting, code completion, context sensitive editors, and more advanced features save you most of the typing.

To get a feel of the DOs and DON’Ts about the Delphi with statement.

Let’s start with this simple VCL example:

procedure TForm1.Edit1Click(Sender: TObject);
begin
  with Edit1 do
  begin
    ShowMessage(Format('%s: %s', [Name, Caption]));
  end;
end;

You might think that this code shows something like “Edit1: Edit1”. But it shows “Edit1: Form1”, as a TEdit does not have a Caption property (it has a Text property), but TForm has a Caption property.

procedure TForm1.Button1Click(Sender: TObject);
begin
  with Button1 do
  begin
    ShowMessage(Format('%s: %s', [Name, Caption])); // put a breakpoint here
  end;
end;

More drawbacks

Put a breakpoint on the indicated line, then observe the debugger takes the Name and Caption properties from Form1, not of Button1. You can find another example of this debugger behaviour here.

with also can prevent some refactorings (both the stock Delphi ones, and ones available through for instance ModelMaker Code Explorer and Castallia).

 Nesting

Red lights should start flashing when you add multiple clauses into one with statement, as that usually widens up the scope so much that in stead of benefiting from it, you will feel pain as If With is Considered Harmful, What About Double With? shows: where does Enabled belong to?

    with LMargins, GlassFrame do
      begin
        if Enabled then
        begin
          if not SheetOfGlass then
          begin
            cxLeftWidth := Left;
            cxRightWidth := Right;

Sometimes, the use of with is sign of a need for refactoring, for instance shown in Is Delphi “with” keyword a bad practice?:

procedure TMyForm.AddButtonClick(Sender: TObject);
begin
  with LongNameDataModule do
  begin
     LongNameTable1.Insert;
     LongNameTable1_Field1.Value := 'some value';
     LongNameTable1_Field2.Value := LongNameTable2_LongNameField1.Value;
     LongNameTable1_Field3.Value := LongNameTable3_LongNameField1.Value;
     LongNameTable1_Field4.Value := LongNameTable4_LongNameField1.Value;
     LongNameTable1.Post;
  end
end;

The above code has a big dependency on the LongTableDataModule and is business logic. So this business logic should be refactored out of the form into the data module. Not doing so violates Law of Demeter, which is basically about Loose Coupling.

procedure TLongNameDataModule.AddToLongNameTable1(const NewField1Value: string);
begin
  LongNameTable1.Insert;
  LongNameTable1_Field1.Value := NewField1Value;
  LongNameTable1_Field2.Value := LongNameTable2_LongNameField1.Value;
  LongNameTable1_Field3.Value := LongNameTable3_LongNameField1.Value;
  LongNameTable1_Field4.Value := LongNameTable4_LongNameField1.Value;
  LongNameTable1.Post;
end;

Then call it from the form like this:

procedure TMyForm.AddButtonClick(Sender: TObject);
begin
  LongNameDataModule.AddToLongNameTable1('some value');
end;

This effectively gets rid of the with statement, and makes the code more maintainable: kill two birds with one stone.

One of the few places I still use with is like here (thanks Jamey McElveen):

with TMyForm.Create(nil) do
try
  ShowModal();
finally
  Free();
end;

and here (thanks Brian Frost):

procedure ActionOnUpdate(Sender: TObject)
begin
  with Sender as TAction do
    Enabled := Something;
end;

Alternatives

Both VB6 (Visual Basic) and Oxygene have alternatives for the with statement. It is still there in VB.NET, and still raises controversies.

I didn’t know it came from SmalTalk, but I did know C# 3 introduced a very confined similar construct for initialization.

Back to Visual Basic: in VB6, you have to prepend all the usage with a dot (.) which used in Delphi code would make your code look like this:

begin
  with MagicalFrame.Label1 do
  begin
    if .Font.Color = clBlue then
    begin
      .Font.Color := clRed;
      .Caption := 'Red';
    end
    else
    begin
      .Font.Color := clBlue;
      .Caption := 'Blue';
    end;
  end;
end;

In Oxygene, you can introduce a temporary variable in a with statement.

Eliminating with

I know of two tools to eliminate the with statement.

I have a lot of experience with the latter, but Castallia should work just as good.

–jeroen

via: Delphi: you should avoid the with statement as it makes your code less future proof « The Wiert Corner – irregular stream of stuff.

9 Responses to “Delphi: `with` dos and dont’s (more of the latter though).”

  1. Carlos said

    lots of times, a code with “with” is more readable / faster / smaller than using new functions on another unit and passing variables as parameters…

    • jpluimers said

      I disagree with readable in many cases, especially when the statements get longer or ambiguity gets introduced. When it gets more readable using `with`, feel free to use it.

      A method might be marginally slower or larger than a `with` statement as passing parameters (especially const ones) have very little overhead compared to a list of assignments. So that is only an argument if performance really matters.

  2. Sven said

    Hi!

    First: Can it be that the code listing for the “put your breakpoint here” is at the wrong position and should be much further down your text? (for me it’s in the “Now” section while it AFAIK should be in the “More drawbacks” one)

    Second: Also regarding the Debugger case: Lazarus can correctly pick up the scope of the variables/properties, so that is just a shortcoming of either the debug information the Delphi compiler generates or of the Delphi debugger.

    Regards,
    Sven

    • jpluimers said

      Thanks for the correction. Changed it. I’d love to blame the WordPress editor, but this time it was my own editing mistake (:

      You are right about the debugger. In Lazarus, the debugger grew with the language. In RAD Studio, that is not the case (I think the debugger came from the C/C++ side, maybe even from the Turbo Debugger and Turbo Debugger for Windows side), hence the debug information format does not fully cover the Delphi syntax format.

      On the Delphi mobile side, it is different too. The compiler back-end is LLVM based, but because LLDB is not ready for prime-time, the debugger is GDB based. Different kind of debug information. Different behaviour.

  3. Navid said

    Rather than eliminating the With statement, it would better be fixed. Visual Basic got it right with its . (dot) requirement which clears up ambiguities:

    Instead of:
    With Mem1 do
    begin
    Text:= ‘New text’;

    you’d write:
    With Mem1 do
    begin
    .Text:= ‘New text’;

    • Navid said

      And with a refresh I see you already covered it – feel free to moderate my comment away … Best

      • jpluimers said

        No problem. It is one of the things I’d like that they add to the `with` statement: allow for an alias, then emit a compiler hint or warning when you do not use an alias.

        Thanks for bringing it up, as I forgot about writing about the addition I’d like.

  4. sglienke said

    “You might think that this code shows something like “Form1: Edit1″. But it shows “Form1: Form1″, as a TEdit does not have a Caption property (it has a Text property), but TForm has a Caption property.”

    Actually it should say:

    “You might think that this code shows something like “Edit1: Edit1″. But it shows “Edit1: Form1″, as a TEdit does not have a Caption property (it has a Text property), but TForm has a Caption property.”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

 
%d bloggers like this: