Methods are the building block of any program, as they contain what we commonly think of as "code": the actual logic that makes up the functionality of a class.
Simply put, a method is a block of code that can be called from elsewhere within the project to perform its function.
Methods are Object Oriented Programming's logical replacement for the plain "procedures" and "functions" defined by basic Pascal. Because methods are such an important part of OOP languages, and because what we call something defines how we think of it, Oxygene introduced the new
method keyword back in 2004 to define methods.
Method Declaration Syntax
In its simplest form, a method declaration consists of the
method keyword, followed by a method name and an optional list of parameters that can be passed to the method, in parenthesis. Methods can also have an optional result type, separated from the rest of the method with a colon.
The method declaration can optionally be followed by a number of Modifiers, separated by semicolons. The most important modifiers will be covered in more detail, below.
method MyMethodName(aParameter: String; aNotherParameter: Integer): Boolean; virtual;
Method Implementation Syntax
Unless it is
external (more on those below), each method declaration needs to provide an implementation. The implantation can be provided right behind the declaration, suing what Oxygene refers to as Unified Class Syntax, or it can be provided below the type declaration in the
implementation section of the file.
If the implementation is provided separarely, the method declaration (minus any modifiers) is repeated, but expanded to include the type name in front of the method name:
method MyClass.MyMethodName(aParameter: String; aNotherParameter: Integer): Boolean; begin // code goes here end;
The method body must consist of at least a
end block that can contain the zero or more Statements that make up the method body. There can also optionally be a
require block at the beginning and an
ensure block at the end, both containing pre- and post-conditions to be checked before the method starts and after it ends. Pre- and post-conditions are covered in more detail, below.
For compatibility with legacy Pascal compilers such as Delphi, methods can also have an optional
var section at the start, where variables for use throughout the method can be declared. Use of a
var section is discouraged in favor of using
var Statements in the main body of the method body, as needed.
method MyClass.DoSomeWork(aData: String): String; var lCount: Integer; // not recommended begin lCount := aData.Length; //... end;
A method can take zero or more parameters, listed after the method name, and enclosed in parenthesis. Individual parameters are separated by semicolons, and each parameter must at the very least consist of a name and a type, separated by a colon.
Unlike other languages, including C# and Swift, an empty set of parenthesis is optional (and discouraged) for declaring methods that take no parameters.
method DoSomething(aParameter: String); method DoSomethingElse;
Optionally, a default value can be provided via the
:= operator. If a default is provided, callers can omit the parameter in question, and the default will be used instead:
method DoSomething(aParameter: String := 'Use this by default.');
'out' and 'var' Parameters
Parameters can also have a modifier of either
out. By default, parameters are passed into the method, but not back out. Inside the method, the parameter becomes a local variable, and any changes made to it inside the method are not propagated to the caller.
Parameters declared with
var will be passed into the method, and any changes made by the method will be passed back out when the method exits. Parameters declared with
out will start off as having their
default() value when the method starts, and any changes made to them will then be passed out when the method is finished.
method GetValues(var a: Integer; out b: Integer); begin a := a + 15; b := 10; end;
Note that when passing heap-based Classes, the
out modifiers apply to the reference, and not the data stored within the type. Changes made to a class passed in as regular "in-wards" parameter will still affect the caller, as it is holding as reference to the same class. Changing the parameter to a different instance will not propagate outwards. When a reference type is passed via
var, changes to the reference itself will propagate back, and the caller will have a reference to the new object.
method GetValues(a: SomeClass; var b: SomeClass); begin a.SomeValue := a.SomeValue+15; // affects the instance that the caller passed in a := new SomeClass(); // this change will not affewct ehe caller b := new SomeClass(); // this change will affect ehe caller end; GetValues(x, y); // x is changed, but still the same class. y is now a new class
'params' Method Parameters
params keyword can be used on the last parameter to capture all extra parameters in a single array. To use params, this parameter has to be a simple Dynamic Array.
method Write(a: String; params args: array of String); begin end; ... Write('hello', 'this', 'world'); // a is 'hello', args is ['this', 'world'] Write('hello', 'world'); // a is 'hello', args is ['world'] Write('hello'); // a is 'hello', args is  (empty array)
If the method is defined to return a value, an implicit
result Variable is available in scope, within the method body. This variable is of the same type as the method's return type, and initialized to the
default() value of the type (typically
result variable can be both written and read, allowing you to use it to store an incremental status and be updated during the course of the method's execution. This is a significant improvement over other languages, including C, C# or Swift, where a return value can only be set in the same step as the method is exited.
Of course the
exit Statement can still exit the method and return a value in one go at any time. If
exit is called with a value, that value is used as result. If it is called without value, any previously set value in
result will be used, just as if the method terminated by reaching its end.
Multi-Part Method Names
In order to fit in well with the API conventions on the Cocoa platform, Oxygene has support for multi-part method names.
Multi-part method names are essentially the ability for a method's name to be split into separate parts, each followed by a distinct parameter. This is "required" on the Cocoa platform because all the platform's APIs follow this convention, and we wanted Oxygene to be able to both consume and implement methods alongside those conventions without resorting to awkward attributes or other adornments, and to feel at home on the Cocoa platform.
A multi-part method has parameter parts for each part, both when being declared:
method RunCommand(aString: String) Arguments(params aArguments: array of String): Boolean;
...and when being called:
myClass.RunCommand('ebuild') Arguments('MyProject.sln', '--configuration:Debug');
While the feature was created for Cocoa, multi-part method names are supported on all platforms, and considered a regular feature of the Oxygene language (and also compatible with C#, Swift and Java). We encourage to use and embrace them, as they are a great tool to make code more readable and understandable.
The method body is what makes up the bulk of a method, and provides its implementation. It starts with the
begin keyword and ends with a matching
end or – id a post-condition is provided –
The method body is made up of xero or more Statements, separated by semicolons (and, traditionally but optionally, a line break). Most (mut not all) Expressions can also be used as standalone statements. When doing so, the value of the expression is simply ignored.
Statements can be simple one-line constructs (such as a Method Call or a
var Declaration, or they can be more complex structures that might even contain their own sub-list of statements (such as a
Pre- and Post-Conditions
Methods can provide optional pre-conditions that will be checked before the main mody of the method starts executing, or post-conditions that will be checked after the method exits.
Both pre-conditions and post-conditions consist of a list of Boolean expressions, separated by semicolons. Each condition will be evaluated in row, and if any of them evaluates to
false, execution will be aborted with a fatal .
Pre-conditions are provided before the method body, in an optional
require section. Post-conditions are provided after the method body, in an optional
method MyClass.IncrementCount(aBy: Int32); require aBy > 0; begin fCount := fCount + aBy ensure fCount - aBy = old fCount; end;
ensure section, the can be used to refer to the original value a parameter or field had before the method started. This can be useful for checking the validity od the result compared to previous state. Note that for heap-based (except Strings such as Classes, which receive special handling), the
old operator only captures the old reference, but not the old state of the object it contains.
In both the
ensure section, an optional more detailed error message can be provided, to be included in the assertion when a check fails. This must be in the form of a constant String Literal, separated from the expression with a colon. (If the expression itself contains a colon, the whole expression needs to be wrapped in parenthesis).
method MyClass.IncrementCount(); require (SomeValue:SomeField = 0) : "SomeField may not be zero!"; begin ...
Note that pre- and post-conditions will only be compiled and executed if the "Enable Asserts" Compiler Option is on. By default, this option is off for Release builds, to optimize performance. It is important to not rely on pre- and-postconditions to execute for the regular operation of the project, and to avoid conditions with side effects.
Pre- and post-conditions should be used only for testing, not for general code flow.
Similar to Generic Types, individual methods can be declared to have one or more generic parameters. The type parameters are provided enclosed in angle brackets after the method name. These types are then used as placeholders for a more concrete type, allowing you to define methods that are, well, generic, and not tied down to working with a specific type as parameter or result.
method ArrayHelpers.FindIndexInArray<T>(list: array of T; item: T): Integer; begin for i: Integer := 0 to list.Length - 1 do begin if list[i] = item then exit i; exit -1; end;
For example, the
FindIndexInArray<T> method above can work with any type of array to find an item's index.
ArrayHelpers.FindIndexInArray<Int>([1,2,3], 2); ArrayHelpers.FindIndexInArray<String>(["One","Two","Three"], "Four");
method ArrayHelpers.SomeMethod<in T>(aSomeParaneter T); where T has constructor; begin ...
Please refer to the Generic Types topic for a more detailed discussion on generics, including many details that apply to generics in members, as well.
Like most type members, methods are by default defined on the instance – that means the method can be called on and will execute in the context of an instance of the class. A method can be marked as static by prefixing the method declaration with the
class keyword, or by applying the
static Member Modifier:
class method MyClassMethod: String; // static method on the class itself method MyOtherClassMethod: String; static; // static method on the class itself
method OverrideMe; virtual;
A number of other Member Modifiers can be applied to methods:
mapped to(See Mapped Members)
optional(Interface members only)
partial(See Partial Types)
unsafe(See on .NET)
where(see Generic Methods, above
For compatibility with legacy Pascal languages, the
function keywords can be used instead of
method, when Delphi Compatibility is turned on. Methods declared with
function must have a result type, while those declared with
procedure may not.
We discourage the use of
function and strongly encourage you to embrace the