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:
- Is Delphi “with” keyword a bad practice? – Stack Overflow.
- Why should I not use “with” in Delphi? – Stack Overflow.
- If With is Considered Harmful, What About Double With?.
- Movie #19 – Hate With a Passion.
-
“The with keyword. The most hideous, dangerous, blow-your-own-feet-off feature in the language.” by Verity Stob
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.
- Bruce McGee indicated One of the Castalia refactoring tools is named “Eliminate ‘WITH'” in delphi – Tool to refactor “with” blocks – Stack Overflow.
- As of ModelMaker Code Explorer 9, there is a with statement converter.
I have a lot of experience with the latter, but Castallia should work just as good.
–jeroen
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.
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.
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.
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.”
jpluimers said
Thanks for catching that.
Will fix that in a minute.Fixed. Thanks again.