The STATIC modifier has a different meaning depending on whether it is used on a class or its members, on a FUNCTION/GLOBAL or on a LOCAL variable.
The STATIC modifier on a member of a class (method, constructor, property, field, event) declares a member which belongs to the class itself, instead of to an instance of the class. Normal (instance) methods and other members can be called by using an instance of the class (for example through SELF, or a local variable holding such an instantiated object) with the colon (":") operator (also with the dot "." operator if the compiler option /allowdot is enabled), while static members can be accessed directly through the class itself, using the dot operator:
FUNCTION Start() AS VOID
LOCAL oInstance AS TestClass
oInstance := TestClass{}
? oInstance:instance_field
? oInstance:InstanceMethod()
? oInstance:InstanceProperty
? TestClass.static_field
? TestClass.StaticMethod()
? TestClass.StaticProperty
CLASS TestClass
EXPORT instance_field := "instance field" AS STRING
STATIC EXPORT static_field := "static field" AS STRING
METHOD InstanceMethod() AS STRING
RETURN "Instance method"
PROPERTY InstanceProperty() AS STRING GET "Instance property"
STATIC METHOD StaticMethod() AS STRING
RETURN "Static method"
STATIC PROPERTY StaticProperty() AS STRING GET "Instance property"
END CLASS
A static method is very similar to a common function and can be used in the same way, but it has the advantage that it is encapsulated within a class that may contain other related static or non-static members. Like functions, static methods cannot use instance members of the class with SELF; but they can access other static members. Methods with related functionality can be grouped together under the same class, offering much better intellisense support in the editor (typing a dot after a class name will show a list of all its static members) and makes the code more structured. For example, the System.Math class offers a lot of mathematical functions, all grouped together under a single class, making it easier to find and use, rather than having several standalone functions offering the same functionality. Similarly, a static field can be regarded as a GLOBAL, but again structured under a class. Any method of a class that is not using SELF in its body (so is not accessing any instance members) is a good candidate to be declared as static.
It is also possible to define a single static constructor per class, which is guaranteed to be called automatically just before any static member (method, field, etc.) of the class is accessed for the first time. A static constructor must have no parameters and cannot be overloaded. Typical use of a static constructor is to initialize static fields:
FUNCTION Start() AS VOID
? TestClass.static_field // adjusted by static constructor
CLASS TestClass
STATIC EXPORT static_field := "initial value" AS STRING
STATIC CONSTRUCTOR()
? TestClass.static_field // initial value
TestClass.static_field := "adjusted by static constructor"
END CLASS
Note that it is not allowed to define a static destructor. If you need to clean up data stored inside a static class, it is recommended to register a ProcessExit event handler in the AppDomain class.This will be called when the application ends.
FUNCTION Start() AS VOID
CLASS TestClass
STATIC CONSTRUCTOR()
AppDomain.CurrentDomain:ProcessExit += EventHandler{CurrentDomain_ProcessExit}
static method CurrentDomain_ProcessExit(sender as object, e as EventArgs) as void
// clean up
return
END CLASS
When a class is designed to hold only static members, it can be declared as being static itself. Doing so allows the compiler to check every member of the class and ensure that none are accidentally declared as non-static:
STATIC CLASS StaticClass
STATIC EXPORT static_field AS STRING
EXPORT accidental_instance AS STRING // compiler error XS0708: cannot declare instance members in a static class
STATIC METHOD Static_method() AS VOID
METHOD Accidental_Instance_method() AS VOID // compiler error XS0708: cannot declare instance members in a static class
END CLASS
Declaring a function or global as static, restricts its visibility to the code file where it is declared only. This allows multiple files to declare functions and globals with the same name, with each being visible only within its respective file:
STATIC GLOBAL GlobalVisibleOnlyInThisFile AS INT
STATIC FUNCTION FunctionVisibleOnlyInThisFile() AS VOID
Declaring a LOCAL variable as STATIC, causes it to be initialized with a value only once, being the first time that the method, function or other entity where it is declared is called. The local variable retains its previous value on subsequent times the code is called:
FUNCTION Start() AS VOID
? TestStaticLocal() // 2
? TestStaticLocal() // 3
? TestStaticLocal() // 4
FUNCTION TestStaticLocal() AS INT
STATIC LOCAL nStaticLocal := 1 AS INT // gets initialized to 1 only the first time the function is called
nStaticLocal ++ // the current value will be retained the next time the function is called
RETURN nStaticLocal