Originally published at: https://landgraf.dev/en/5-reasons-to-use-inline-variables-in-delphi/
Inline variables declaration is a feature introduced in Delphi Rio 10.3. What is it?
In short, it is the possibility to declare a variable in any line of your code. That is, you can declare a variable this way, within the begin..end block:
procedure Test;
begin
var I: Integer;
I := 22;
ShowMessage (I.ToString);
end;
A lot of people already understood how this feature works, but did not understand why it is interesting. In this article, I will show you this new feature with a focus on the advantages it brings.
1. Organizes your code
The variable is only accessible from the point it is declared. For many people this better organizes the code in a large method, because it is possible to know better where that variable is being used. Consider the following code:
procedure Test;
var
A, B, C, D, E: Integer;
Found, Done, Excluded: Boolean;
Text: string;
begin
// many
// lines
// of
// code
end;
It may be confusing to know where all of these variables are used, when they are being initialized, if it has been set a value before, etc. In the following code, we know that the Text variable, for example, does not exist at the beginning of the code, and that it is only used at the end. No code changed its value bfore that part of the code:
procedure Test; begin var A, C: Integer; // We cannot use Text here // lines // of // code
var Text: string;
// Text can only be used here
end;
Update (May 18, 2020): Darian Miller provides even more benefits of using inline variables in his nice blog post “Newly discovered hidden benefits of inline variables in Delphi“.
2. Minimizes bugs
Have you ever done something like this:
procedure Test;
var I: Integer;
begin
for I := 0 to Count - 1 do
Process;
DoSomethingWithI(I);
end;
That is, using the for variable after the loop is finished. This is not safe, and although the compiler raises a warning for this, many people ignore it. By declaring the for variable inline, it will be only valid inside the for, and using it outside the block will result in a compilation error:
procedure Test;
begin
for var I: Integer := 0 to Count - 1 do
Process;
DoSomethingWithI(I); // Compile error!!!
end;
The benefit from the code above comes from the fact that the scope of the variable is limited to the block in which they are declared. That minimizes the chance of errors. For example, suppose you have a code like this:
procedure Test;
var I: Integer;
begin
I := CalculateSomething;
Persist(I);
// many lines below...
Log(I);
end;
Then you eventually need to refactor the code in a way that the first part only executes under a specified condition. You think that variable I is only being used there, and do something like this:
procedure Test;
var I: Integer;
begin
if Condition then
begin
I := CalculateSomething;
Persist(I);
end;
// many lines below...
Log(I);
end;
There, you forgot the last line and maybe the value of I is not what you expect. Changing the scope of your variable to the block will generate compilation errors if the variable is used outside the block, which will show you the problem immediately, so you can make a decision:
procedure Test;
begin
if Condition then
begin
var I: Integer;
I := CalculateSomething;
Persist(I);
end;
// many lines below...
Log(I); // Compile error!
end;
3. Less typing
Who does not want more productivity? If you can type a bit less to declare a variable, why not? You can now declare and initialize a variable at the same time:
procedure Test;
begin
var I: Integer := 22;
ShowMessage (I.ToString);
end;
But not only that. There is also type inference, which means that in most cases you do not need to include the variable type when declaring it. Just initialize the variable with a value and Delphi will know the variable type.
Looks like it’s not a big deal? Imagine a case where the variable type is using heavy generics:
procedure NewTest;
var
MyDictionary: TObjectDictionary<string, TObjectList<TMyAmazingClass>>;
Pair: TPair<string, TObjectList<TMyAmazingClass>>;
List: TObjectList<TMyAmazingClass>;
begin
MyDictionary := TObjectDictionary<string, TObjectList<TMyAmazingClass>>.Create;
MyDictionary.Add('one', CreateList);
Pair := MyDictionary.ExtractPair('one');
List := Pair.Value;
ShowMessage(List.Count.ToString);
end;
Using inline variable and type inference, you can rewrite the code this way:
procedure NewTest;
begin
var MyDictionary := TObjectDictionary<string, TObjectList<TMyAmazingClass>>.Create;
MyDictionary.Add('one', CreateList);
var Pair := MyDictionary.ExtractPair('one');
var List := Pair.Value;
ShowMessage(List.Count.ToString);
end;
Better, isn’t it?
4. Increases performance
The fact that the variables belong to a more limited scope (within a begin..end block) can even increase code performance!
You can see more details in this excelent article: Inline Variables can increase performance. In summary: the variable will initialized only if the code execution enters the block, and finalized only upon block exit. In this code, for example:
procedure TestInlineVars(const ACondition: Boolean);
begin
// BEFORE
if (ACondition) then
begin
var S := 'Inline String';
var I: IInterface := TInterfacedObject.Create;
var F: TFoo;
F.S := 'Managed Record';
end;
// AFTER
end;
Variables S, I and F are managed types (string, interface and record). The compiler automatically adds initialization and finalization code for them.
If you call TestInlineVars procedure a million times, it will have a big impact. However with the code above, the variables will only be initialized if ACondition is true and the block is actually executed. Less unnecessary code being executed.
5. Makes it easier to use conditional directives
This feature can help even in small things. This article brought my attention: Unexpected Benefit of Inline Variables: Conditional Blocks.
If you use compiler directives where you declare and use different variables for each situation, you have also wrap variable declarations around a compiler directive as well:
procedure DoesSomething;
var
{$IFDEF CASE1}
var1: Integer;
{$ENDIF}
{$IFDEF CASE2}
var2: Integer;
{$ENDIF
begin
{$IFDEF CASE1}
// use var1
{$ENDIF}
{$IFDEF CASE2}
// use var2
{$ENDIF}
end;
Boring, huh? In my opinion this is easier:
procedure DoesSomething;
begin
{$IFDEF CASE1}
var1: Integer;
// use var1
{$ENDIF}
{$IFDEF CASE2}
var2: Integer;
// use var2
{$ENDIF}
end;
I believe that inline variables still brings other subtle benefits in specific situations that are not listed here. If you can think of any other benefit, leave your comment. If you do not agree and you think inline variables are not good news for Delphi, leave your comment as well. Just do not forget one thing: if you didn’t like it, simply don’t use it!