Recently, I had to enhance some legacy codes running into thousands and thousands of lines of code within a loop. In order to facilitate ease of customisation, I decided to refactor the codes breaking chunks of codes into procedures. However, I was concerned with the visibility of the visibility in these procedures of variables declared in the calling procedure.
A google search led me to this valuable article at delphi.about.com/od/beginners/l/aa060899.htm
Object Pascal Variable Scope
As mentioned in some of the
previous articles understanding Object Pascal
variable scope is one of key elements in building applications with Delphi/Object Pascal.
Scope of Variables and Constants
The term
scope refers to the availability of a
variable or constant declared (or used) in one part of a program to other parts of a program.
Unless
we specify otherwise, changing the value of a variable named, let's
say, SomeNumber in one procedure (function) will not affect another
variable with the same name in another procedure (function).
Since Delphi requires us to
declare variables, it's a lot
harder to fall into the trap caused by side effects accidentally. As we
know by now, every variable used in some procedure has to be declared in
the
var section of the event handler.
In general, we declare a variable where we want to use it. For
example, if we want to use a variable in an event handler, we declare
the variable within the event handler.
Local Scope (+ variable declaration and initialization)
Most variables have
local scope, which means that the variable is
visible only within the code block in which it is declared (usually:
Event Handler for some method). In particular, an event handler will not
normally have access to the value of a variable in another event
handler.
If we want to be sure a variable is local within an event handler,
we have to declare it in the var section inside the event handler. Since
we must declare a variable before we can use it, if we can use a
variable without declaring it locally, we know that there is a variable
with greater scope with the same name somewhere around project.
Let us look at the first example:
1. Start Delphi, this will give us (by default) new application with one blank form.
2. Double click somewhere on the form (to create OnCreate event handler)
3. Write down this code:
procedure TForm1.FormCreate(Sender: TObject);
begin
ShowMessage(FloatToStr(SomeNumber));
end;
|
4. If you try to run your project now, you will be prompted with:
"Undeclared Identifier: 'SomeNumber'"
error. This means that we haven't declared SomeNumber variable in our
project (note: entire project, not FormCreate event handler).
5. To declare SomeNumber variable as double type change your code to:
procedure TForm1.FormCreate(Sender: TObject);
var SomeNumber: double;
begin
ShowMessage(FloatToStr(SomeNumber));
end;
|
6. Run your project, message box will appear with some strange
(value of the memory region where variable is stored) number. Delphi
will also give us
"Variable 'SomeNumber' might not have been initialized"
warning. This means that, before using declared variable, it is a good
practice to initialize it (just to be sure). For that purpose add this
line of code before ShowMessage...
7. Now, when you run your project message box will display 123,45 (no errors, no warnings).
Finally, we can see why local variables are called local...
8.Add one TButton component to form and double-click it to create
Buttons OnClick event handler. Add the following code (so that OnClick
looks like):
procedure TForm1.Button1Click(Sender: TObject);
begin
ShowMessage(FloatToStr(SomeNumber));
end;
|
Again we have
"Undeclared Identifier: 'SomeNumber'" error.
This is what local variables are all about: even if we have declared
(and initialized) SomeNumber variable in OnCreate event handler of the
form, SomeNumber is not accessible in the OnClick handler of TButtton.
We simply cannot use SomeNumber (with 123.45 value) in the OnClick (at
least for now). This means that SomeNumber from OnCreate and SomeNumber
from OnClick are two different variables (that can, of course, hold
different values)
9. Don't close this project, jet. We will need it again...
10. Add the following line before ShowMessage in the OnClick event
handler (we will need it later, don't worry about this for now)
Sharing variables across procedures (event handlers)
Occasionally we will want to share the values of variables (and
constants) across event handlers or across units. For example, if an
application is designed to perform a calculation involving one
SomeNumber at a time, that SomeNumber should be available to all
procedures in a unit.
Depending on where we declare a variable, the variable can be thought of as a true
global variable accessible by any other code in the application, or a
unit-level variable accessible by any code in the unit.
Unit level variables - unit level scope
We put the declaration statements for unit-level variables in a
var section in the unit's
implementation section. Unit-level constants are declared in a const section.
Let's look at the second example:
0. We will be modifying our first example (be sure to have it)
1. Add declaration of SomeNumber, so that implementation code of the unit looks like:
...
implementation
{$R *.DFM}
var
SomeNumber: Double;
...
|
2. Run your program. As you can see, we don't have
"Undeclared Identifier: 'SomeNumber'"
error in OnClick handler of the TButton. When program starts message
box will appear with 123.45 value. When you click on the Button1 message
box will display 555.55; that's why we need step 10 in the first
example - we have
initialized SomeNumber to 555.55 in the OnClick event of the Button1.
What's this? We have two SomeNumber variables in our project and they both hold different values.
Obviously, we have to be careful when assigning values to
unit-level variables. Although we can use the same variable (or
constant) name for both local and unit-level variables, this is not a
good idea. Any var (or const) declaration contained in a procedure takes
precedence over global (unit-level) declarations. Duplicating the names
makes the global variable invisible to the procedure (Delphi doesn't
tell us whether a global variable has been defined with the same name as
a local variable). That is why SomeNumber holds the 123,45 value in the
OnCreate event handler of the form (we cannot use global variable
SomeNumber in the OnCreate procedure)
Note 1: If
you really have to use two variables with the same SomeNumber name (one
global and one local), you can access the global SomeNumber variable
value in the forms OnCreate procedure with the call to unit1. SomeNumber
(unit1 is the name of the unit with the global SomeNumber variable).
That is, something like
unit1.SomeNumber:=444.44;
|
will change value of the global variable SomeNumber inside OnCreate
event handler of the form (remember that there is a SomeNumber variable
local to this procedure which will stay unchanged)
Note 2: As
global SomeNumber is global to the unit we can access (more important:
change) its value from any other procedure inside this unit. However
click to Button 1 will reset SomeNumber value to 555.55. Better way to
initialize global variables is inside
initialization section of the unit.
Global variables - program level scope
If we want to create true global variables (or/and constants) in a project, we have to place the declaration in the
interface section of the unit. Variables declared in the interface section will be visible (accessible) to any unit which
uses that unit.
For example, to change SomeNumber variable value that is declared in Unit1 from Unit2, use this statement:
Unit1.SomeNumber:=999.99;
|
Be sure to add Unit1 to the uses clause of Unit2.
Conclusion
That's it. I hope you have had the power to come to the end of this
article. As we can see, there is much to be stated about variable scope
in Object Pascal. Of course, there is more:
static variables (or
typed constants) are something we could name "constant variables". I'll
be dealing with static variables in some of the
future articles...