Oxygene (programming language)
![]() | The topic of this article may not meet Wikipedia's general notability guideline. (October 2009) |
![]() | This article contains promotional content. (October 2009) |
![]() | It has been suggested that this article be merged into Delphi Prism. (Discuss) Proposed since December 2009. |
![]() | |
Developer | RemObjects Software |
---|---|
Stable release | 3.0.21
/ August 29, 2009 |
OS | Common Language Infrastructure |
License | Commercial |
Website | Oxygene Homepage |
Influenced by | |
Object Pascal, C# |
Oxygene (formerly known as Chrome) is a programming language developed by RemObjects Software for the Common Language Infrastructure. Oxygene is Object Pascal-based. Compared to the now deprecated Delphi.NET, Oxygene does not emphasize total backward compatibility, but is designed to be a full .NET citizen and leverage all the features and technologies provided by the .NET runtime.
Starting 2008, RemObjects Software has licensed its compiler and IDE technology to Embarcadero to be used in their Delphi Prism product.[1] Delphi Prism offers full integration into Visual Studio 2005/2008. If the user doesn't have Visual Studio installed, Oxygene's setup includes the Visual Studio 2008 Shell.
Oxygene Language
The Oxygene language has its origins in Object Pascal in general and Delphi in particular, but was designed to reflect the guidelines of .NET programming and to create fully CLR-compliant assemblies. Therefore not all language features known from Object Pascal / Delphi can be found any longer in Oxygene or only as legacy features.
Object Oriented Programming
Oxygene is an object-oriented language, which means it uses classes, which can hold data and execute code, to design programs. Classes are "prototypes" for objects, like the idea of an apple is the prototype for the apple you can actually buy in the shop. You know, that an apple has a color and you know, that it can be peeled: that are is data and executable "code" for the apple class.
Parallel Programming
Oxygene provides language-level support for some of features of parallel programming. The goal is to use all cores or processors of a computer to improve performance. To reach this goal, tasks have to be distributed among several threads. The .NET framework's ThreadPool
class offered a way to efficiently work with several threads. The Task Parallel Library (TPL) was introduced in .NET 4.0 to provide more features for parallel programming.
Operator Overloading
Operators can be overloaded in Oxygene using the class operator
syntax:
class operator implicit(i : Integer) : MyClass;
Note, that for operator overloading each operator has a name, that has to be used in the operator overloading syntax, because for example "+" would not be a valid method name in Oxygene[2].
Program Structure
Oxygene does not use "Units" like Delphi does, but uses .NET-namespaces to organize and group types. A namespace can span multiple files (and assemblies), but one file can only contain types of one namespace. This namespace is defined at the very top of the file:
namespace ConsoleApplication1;
Oxygene files are separated into an interface and an implementation section, which is the structure known from Delphi. The interface section follows the declaration of the namespace. It contains the uses
-clause, which in Oxygene imports types from other namespaces:
uses
System.Linq;
Imported namespaces have to be in the project itself or in referenced assemblies. Unlike in C#, in Oxygene you cannot define alias names for namespaces, only for single type names (see below).
Following the uses
-clause a file contains type declarations, like they are known from Delphi:
interface
type
ConsoleApp = class
public
class method Main;
end;
As in C#, the Main-method is the entry point for every program. It can have a parameter args : Array of String
for passing command line arguments to the program.
More types can be declared without repeating the type
-keyword.
The implementation of the declared methods is places in the implementation section:
implementation
class method ConsoleApp.Main;
begin
// add your own code here
Console.WriteLine('Hello World.');
end;
end.
Files are always ended with end.
Examples of Oxygene .NET Features
Types
As a .NET language, Oxygene uses the .NET type system: There're value types (like structs) and reference types (like arrays or classes).
Although it does not introduce own "pre-defined" types, Oxygene offers more "pascalish" generic names for some of them[3], so that for example the System.Int32
can be used as Integer
and Boolean
(System.Boolean
), Char
(System.Char
), Real
(System.Double
) join the family of pascal-typenames, too. The struct character of these types, which is part of .NET, is fully preserved.
Type Visibility
As in all .NET languages types in Oxygene have a visibility. In Oxygene the default visibility is assembly
, which is equivalent to the internal
visibility in C#. The other possible type visibility is public
.
type
MyClass = public class
end;
The visibility can be set for every type you define (classes, interfaces, records, ...).
Type Aliases
You can define an alias name for types, which can be used locally or in other Oxygene-assemblies, too.
type
IntList = public List<Integer>; //visible in other Oxygene-assemblies
SecretEnumerable = IEnumerable<String>; //not visible in other assemblies
Public type aliases won't be visible for other languages.
Value Types
Records
Records are what .NET-structs are called in Oxygene. They are declared just like classes, but with the record
keyword:
type
MyRecord = record
method Foo;
end;
As they're just .NET-structs, records can have fields, methods and properties, but do not have inheritance and cannot implement interfaces.
Interfaces
Interfaces are very important concept in the .NET world, the framework itself makes heavy use of them. Interfaces are the specification of a small set of methods, properties and events a class has to implement when implementing the interface. For example contains the interface IEnumerable<T>
specifies the GetEnumerator
method which is used to iterate over sequences.
Interfaces are declared just like classes:
type
MyInterface = public interface
method MakeItSo : IEnumerable;
property Bar : String read write;
end;
Please notice, that for properties the getter and setter are not explicitly specified.
Delegates
Delegates define signatures for methods, so that these methods can be passed in parameters (e.g. callbacks) or stored in variables, etc. They're the type-safe NET-equivalent to function pointers. They're also used in events. When assigning a method to a delegate, one has to use the @
operator, so the compiler knows, that one doesn't want to call the method but just assign it.
Oxygene can create anonymous delegates, so for example you can pass methods to the Invoke
method of a control without declaring the delegate:
method MainForm.MainForm_Load(sender: System.Object; e: System.EventArgs);
begin
Invoke(@DoSomething);
end;
An anonymous delegate with the signature of the method DoSomething
will be created by the compiler.
Oxygene supports polymorphic delegates, which means, that delegates which have parameters of descending types are assignment compatible. Assume two classes MyClass
and MyClassEx = class(MyClass)
, then in the following code BlubbEx
is assignment compatible to Blubb
.
type
delegate Blubb(sender : Object; m : MyClass);
delegate BlubbEx(sender : Object; mx : MyClassEx);
Delegating Interface Implementation
Fields can be used to delegate the implementation of an interface, if the type they're of implements this interface:
Implementor = public class(IMyInterface)
// ... implement interface ...
end;
MyClass = public class(IMyInterface)
fSomeImplementor : Implementor; public implements IMyInterface; //takes care of implementing the interface
end;
In this example the compiler will create public methods and properties in MyClass
, which call the methods / properties of fSomeImplementor
, to implement the members of IMyInterface. This can be used to provide mixin-like functionality[4].
Anonymous Methods
Anonymous methods are implemented inside other methods. They are not accessible outside of the method unless stored inside a delegate field. Anonymous methods can use the local variables of the method they're implemented in and the fields of the class they belong to.
Anonymous methods are especially useful when working with code that is supposed to be executed in an GUI thread, which is done in .NET by passing a method do the Invoke
method (Control.Invoke
in WinForms, Dispatcher.Invoke
in WPF):
method Window1.PredictNearFuture; //declared as async in the interface
begin
// ... Calculate result here, store in variable "theFuture"
Dispatcher.Invoke(DispatcherPriority.ApplicationIdle, method; begin
theFutureTextBox.Text := theFuture;
end);
end;
Anonymous methods can have parameters, too:
method Window1.PredictNearFuture; //declared as async in the interface
begin
// ... Calculate result here, store in variable "theFuture"
Dispatcher.Invoke(DispatcherPriority.ApplicationIdle, method(aFuture : String); begin
theFutureTextBox.Text := aFuture ;
end, theFuture);
end;
Both source codes use anonymous delegates.
Property Notification
Property notification is used mainly for data binding, when the GUI has to know when the value of a property changes. The .NET framework provides the interfaces INotifyPropertyChanged
and INotifyPropertyChanging
(in .NET 3.5) for this purpose. These interfaces define events which have to be fired when a property is changed / was changed.
Oxygene provides the notify
modifier, which can be used on properties. If this modifier is used, the compiler will add the interfaces to the class, implement them and create code to raise the events when the property changes / was changed.
property Foo : String read fFoo write SetFoo; notify;
property Bar : String; notfiy 'Blubb'; //will notify that property "Blubb" was changed instead of "Bar"
As you can see, the modifier can be used on properties which have a setter method. The code to raise the events will then be added to this method during compile time.
Literals
String Literals
String literals can be written with single- or double-quotes. Single-quoted strings have to contain more than one character, otherwise they're treated as character literals. Double-quoted strings can span multiple lines, the line breaks will be part of the string value.
var a := 'a'; //char
var b := "a"; //string
var c := 'ab'; //string
var d := "line
break";
Character Literals
Character literals can be written as single-quoted letters, or using their character number prepended by a "#".
Integer Literals
Integer literals can be written as "normal" decimal numbers, as hexadecimal numbers (prependend with a "$") or as binary numbers (prepended with a "%").
//All variables have the same value:
var a := 16;
var b := $10;
var c := %10000;
Floating Point Literals
By default every floating point literal that has no other type information connected to it, is treated as double. Floating point literals are written as digits, followed by the period as decimal separator and then another sequence of digits. A syntax for exponential write is supported: 31.415926E-1
Identifiers
It must not start with a digit, but may contain them. The underscore "_" is allowed at the beginning and inside an identifier. Keywords can be used as identifiers, if they are escaped with an ampersand "&". A "Special Identifier Syntax" can be used, to use any character in an identifier-name[5].
Identifiers in Oxygene are case-insensitive.
Keywords
Oxygene has global keywords and conditional keywords, that only act as keywords in a special context.
Oxygene Global Keywords | ||||
---|---|---|---|---|
and | as | assembly | begin | break |
case | class | const | constructor | continue |
delegate | div | do | downto | else |
end | ensure | event | except | exit |
false | finalizer | finally | for | forward1 |
function1 | future | if | implementation | in |
inherited | interface | invariants | is | locking |
loop | method | mod | module | namespace |
new | nil | not | nullable | of |
old | on | operator | or | out |
parallel | private | procedure1 | property | protected |
public | raise | record | repeat | require |
result | self | set | shl | shr |
then | to | true | try | type |
unit | until | uses | using | var |
where | while | with | xor | yield |
1These keywords are there for legacy only. |
Oxygene Conditional Keywords | ||||
---|---|---|---|---|
abstract | add | array of | async | default |
each | empty | enum | external | final |
finalizer | flags | global | has | implements |
implies | index | inline | iterator | locked |
matching | nested | notify | override | params |
partial | pinned | read | readonly | reintroduce |
remove | sealed | sequence of | static | step |
unsafe | virtual | where | write | |
The following words are only keywords in query expressions: | ||||
asc | desc | distinct | equals | from |
group by | into | join | on | order by |
reverse | select | skip | take |
Program Structure
Oxygene does not use "Units" like Delphi does, but uses .NET-namespaces to organize and group types. A namespace can span multiple files (and assemblies), but one file can only contain types of one namespace. This namespace is defined at the very top of the file:
namespace ConsoleApplication1;
Oxygene files are separated into an interface and an implementation section, which is the structure known from Delphi. The interface section follows the declaration of the namespace. It contains the uses
-clause, which in Oxygene imports types from other namespaces:
uses
System.Linq;
Imported namespaces have to be in the project itself or in referenced assemblies. Unlike in C#, in Oxygene you cannot define alias names for namespaces, only for single type names (see below).
Following the uses
-clause a file contains type declarations, like they are known from Delphi:
interface
type
ConsoleApp = class
public
class method Main;
end;
As in C#, the Main-method is the entry point for every program. It can have a parameter args : Array of String
for passing command line arguments to the program.
More types can be declared without repeating the type
-keyword.
The implementation of the declared methods is places in the implementation section:
implementation
class method ConsoleApp.Main;
begin
// add your own code here
Console.WriteLine('Hello World.');
end;
end.
Files are always ended with end.
Variables
Variables in Oxygene can be declared in the head of a method or inside its body:
method MyClass.Foo;
var
var1 : Integer; //Variable declared in header
begin
var1 := 5; //Assignment to variable
var var2 : Integer; //Variable declared in the body
var var3 : Integer := 10; //Variable declared and initialized in the body
var var4 := 'foobar'; //String-Variable declared using type inference
end;
As seen in the above example, in the method one can use type inference to declare and initialize a variable without explicitly defining its type.
Constants
Constants can be defined as class members in the interface section or in the header of every method. Their value is defined via "=", not ":=", because it's a definition not an assignment. Type inference works with constants, too.
const pi = 3.1415926;
Their value can only be defined during declaration and will be evaluated at compile time.
Operators
Boolean and Binary Operators
As other Pascal languages, Oxygene does not distinguish between a boolean and
and a binary and
.
The following operators are both boolean and binary: not
, and
, or
, xor
.
Additionally, there's the only-boolean implies
-Operator. It combines two boolean values. The second expression is only evaluated if the first/left expression is true. If the first expression is false, the entire expression will be considered true.
ensure
HasDriversLicense implies Age >= 16;
In combination with is
and in
the not
operator can be used in a more human readable way:
if i not in [1,2,3] then
//...
if i is not String then
//...
Comparison Operators
Oxygene does have the following comparison operators: >=
, <=
, <>
, =
, >
, <
.
In Oxygene you can use the "Boolean Double Comparison":
if -2 <= a <= 2 then
//...
is equal to
if (-2 <= a) and (a <= 2) then
//...
.
Assignment Operators
To assign values in Oxygene, you normally use the :=
operator. For adding and removing event handlers (and only for that), Oxygene does know the "addition assignment operator" +=
and the "subtraction assignment operator" -=
.
Arithmetic Operators
The operators for addition, subtraction, division and multiplication are +
, -
, /
, *
. Although the division-operator used with two integers creates in Oxygene already an integer division, it has still the "old" div
operator (integer division) known from other Pascal languages. The modulo of two integers is calculated using the mod
operator.
Member Access Operators
Oxygene offers two operators for accessing the members of an object. The period operator ".
" works as expected and will raise an exception when used on a nil-value.
Different from the period operator, which requires the value on its left to be assigned (and will usually throw a NullReferenceException if it is not), the colon operator allows to call members on any value, including nil. If the expression on the left of the colon is nil, the entire expression will automatically short-circuit to return nil as well.
var lGreatGrandDadsName := lUser:Parent:Parent:Parent:Name;
Bit Shifting Operators
shl
will shift bits to the left, shr
will shift bits to the right.
Other Operators
@
is used to get the address of something, which for example is used when passing a method as parameter.*
[]
is used to access array elements.
->
is used for lambda expressions.
as
is a casting operator, throwing an exception if the cast is unsuccessful.
is
checks if an object is assignable to a variable of specific type.
in
checks if an element is in a collection of other elements, also used for Set-types.
^
is the pointer dereferencing operator.
iif
is the equivalent to the ternary operator in C-languages: var a := iif(b = 42, 3, 4); //3, if b = 42, 4 else
Operator Overloading
Operators can be overloaded in Oxygene using the class operator
syntax:
class operator implicit(i : Integer) : MyClass;
Note, that for operator overloading each operator has a name, that has to be used in the operator overloading syntax, because for example "+" would not be a valid method name in Oxygene[6].
Control Structures
Blocks of Code
Blocks of code are created by surrounding them with begin
and end
. Variables declared in a code block are not visible / usable outside of it.
Conditional Structures
if
Statement
A complete if
statement in Oxygene consists of the
if {condition} then
{code}
else
{code}
The code can be a single command or a block of code. When nesting if
statements without creating separate code blocks, else
branches always belong to the next inner if
branch, which is indicated by indentation in the following code:
if {condition} then
if {condition} then
{command}
else
{command};
case
Statement
The case
statement executes code, depending on the value of a variable:
case i of
1,2,3: {code}
4..9: {code}
10: begin
{code}
end;
else
{code}
end;
Unlike Delphi, Oxygene also accepts types that are not enumerable such as strings.
The case
statement can also be used to execute code depending on the type of a variable:
case foo type of
Integer: Console.WriteLine(foo.ToString);
Double: Console.WriteLine(foo.ToString('0.00'));
Boolean: Console.WriteLine(iif(Boolean(foo), 'yes', 'no'));
else
Console.WriteLine('Hu? What''s that?');
end;
Loops
for
Loop
In Oxygene, you can (but don't need to) declare the iteration variable inside of the loop's head and its scope will be limited to the loop's body:
for i : Integer := 0 to 42 do
Console.WriteLine(i.ToString);
This even compiles, if there's already a variable with the same name declared. This "outside variable" then can not be accessed from within the loop's body.
for
loops can run backward (using downto
instead of to
) and make steps larger than one (0 to 42 step 4
).
repeat
-until
Loop
The repeat
-until
loop is a post-test loop, which means its body is executed at least one time before the loop-condition is checked:
var i := 0;
repeat
Console.WriteLine("foo");
inc(i);
until i = 10;
while
-do
Loop
The while
-do
is a pre-test loop, the condition is checked before the body is (perhaps) executed the first time.
var i := 0;
while i < 10 do
begin
Console.WriteLine('foo');
inc(i);
end;
loop
Loop
The loop
loop executes its body without any condition indefinitely, until it is aborted using break
(see below).
loop Console.WriteLine('foo'); //will run forever!
var i := 0;
loop begin
Console.WriteLine('foo');
inc(i);
if i = 10 then
break;
end;
for each
(matching
) Loop
With the for each
loop one can iterate over a sequence, i.e. anything that implements IEnumerable
and/or IEnumerable<T>
. The "each
" can be omitted.
for i in Enumerable.Range(1,10) do
Console.WriteLine(i);
As can be seen in the above example, the type of the iterator variable is inferred for typed sequences (i.e. a sequence implementing IEnumerable<T>
).
Only items of a specific type can be filtered with the matching
syntax:
for matching item : IComparable in items do
//...
This will execute the loop body only for items implementing IComparable
and "jumping over" other items in the sequence.
The (zero-based) index of the current iteration can be obtained with the index
syntax:
for item in items index i do
Console.WriteLine((i+1).ToString+'. Item: '+item.ToString);
Controlling Loops
Loops can be controlled by using the break
or continue
keyword. The former aborts the loop completely, the latter aborts the current iteration and starts the next one. Both keywords only affect the most inner loop.
Exception Handling
Oxygene has the try
-except
and try
-finally
statements for handling exception, which can be combined and used together.
try
//Do something very, very dangerous
except
//React if it goes wrong
finally
//Clean-up
end;
The finally
block will be executed regardless of an exception is thrown or not, and if only the finally
block and no except
block is there, the exception will "bubble up" in your program, while in an except
block you have to use the raise;
command to keep re-raise an already handled exception.
You can select on which types of exceptions you want to react:
try
//Do something even more dangerous
except
on e : BioHazardException do
Console.WriteLine('omg, we released a dangerous liquid with the name: '+e.message);
on NuclearException do
Console.WriteLine('Ooops, we set off a nuke!');
on Exception do
Console.WriteLine('Something else went wrong, no idea what');
end;
The last selector is used to catch every exception that wasn't handled before and can be seen as some kind of else-branch. You can also see, that the exception object e
can be used or not.
You can filter exceptions further using the where
-syntax:
try
//You know what goes here ..
except
on e : CategorizedException where e.CategoryId = 42 do
Console.WriteLine('Got excpetion with of category 42');
end;
Types
As a .NET language, Oxygene uses the .NET type system: There're value types (like structs) and reference types (like arrays or classes).
Although it does not introduce own "pre-defined" types, Oxygene offers more "pascalish" generic names for some of them[7], so that for example the System.Int32
can be used as Integer
and Boolean
(System.Boolean
), Char
(System.Char
), Real
(System.Double
) join the family of pascal-typenames, too. The struct character of these types, which is part of .NET, is fully preserved.
Type Visibility
As in all .NET languages types in Oxygene have a visibility. In Oxygene the default visibility is assembly
, which is equivalent to the internal
visibility in C#. The other possible type visibility is public
.
type
MyClass = public class
end;
The visibility can be set for every type you define (classes, interfaces, records, ...).
Type Aliases
You can define an alias name for types, which can be used locally or in other Oxygene-assemblies, too.
type
IntList = public List<Integer>; //visible in other Oxygene-assemblies
SecretEnumerable = IEnumerable<String>; //not visible in other assemblies
Public type aliases won't be visible for other languages.
Value Types
Records
Records are what .NET-structs are called in Oxygene. They are declared just like classes, but with the record
keyword:
type
MyRecord = record
method Foo;
end;
As they're just .NET-structs, records can have fields, methods and properties, but do not have inheritance and cannot implement interfaces.
Enumerations
Enumerations can be defined using the enum
keyword or a shorter syntax:
type
MyEnum1 = public enum(One, Two, Thee);
MyEnum2 = public(One, Two, Three);
By default, the integer value representing one item in the enum is increased by 1
for every item. Declaring a enum using the flags
keyword will assign every item a value of 2i, where i
is the index of the item. You can define the value of an item manually, too.
MyFlags = flags(One, Two, Thee);
MyEnum3 = enum(One = 1, Two = 2, Three = 3);
Sets
Sets are convenient way of working with a small, defined number of ordinal values, like the elements of an enumeration. Every value is exactly zero ore one time in a set.
type
MySet = set of MyEnum;
You can add values to a set or check, if a value is in a set:
var s : MySet := [MySet.One];
s := s + [MySet.Two];
if MySet.Three in s then
Console.WriteLine("Jepp!");
Other operations are subtraction, intersection (*
) and checks for equality, inequality, subset and superset.
Internally sets are implemented using structs and these structs are usable from other languages like C#, too.
Nullable Types
Nullable types are a way, to store whether the value of a value type is set or not. The .NET framework provides the generic Nullable<T>
struct for this purpose, which is integrated into the language in Oxygene.
When declaring e.g. a nullable Integer
var nullableInt : nullable Integer;
you can just assign a "normal" integer to it:
nullableInt := 5;
You can use any method of the value's type on the nullable type:
if nullableInt.CompareTo(3) > 0 then
Console.WriteLine('Is bigger');
You should consider the use of the colon-operator in this case, because it's "nil-safe" and won't throw an exception if the value is not set.
Nullable types in Oxygene can be uses inside expressions[8], where using a "normal" and a nullable type in one expression will in almost every case result in a nullable type.
var k := nullableInt + 5; //k is nullable
Nullables can be used inside boolean expressions, where the equality comparisons (=
and <>
) will always result in non-nullable boolean, all other comparisons will create a nullable boolean:
if nullableInt = 5 then
Console.WriteLine('Jepp!');
To obtain the value of a nullable value, Oxygene provides the system method valueOrDefault
:
var aInt := valueOrDefault(nullableInt, -1);
This will assign the value of nullableInt
, if the value was set, and -1
otherwise. The last parameter can be omitted, then the default value of the non-nullable type will be used as "fallback"-value.
if valueOrDefault(nullableInt > 5) then
Console.WriteLine("Is bigger!")
Here nullableInt > 5
creates a nullable boolean
and valueOrDefault
will return false
, if the value is not set and its value otherwise.
Although there name indicates something else, nullable types are value types (so assigning one to another will create a copy). One can assign them the null
/ nil
value, but only because there is an implicit conversion for this case. For the same reason, you can compare them to nil
in order to find out, if their value was set:
if nullableInt = nil then
Console.WriteLine('Value not set!');
Reference Types
When the type of a variable is reference type, then the variable only holds a reference to an instance of that type. Assigning the variable to another variable just copies the reference, so both variables reference the same instance. When there're no more reference to an instance, it will eventually get removed by the garbage collection (GC).
The null-reference (i.e. the value of a variable not referencing anything) in Oxygene is nil
.
Arrays
Oxygene uses a mixed Pascal / C style for declaring and initializing arrays. The type of an array is declared in the pascal-way
var a : Array of Integer;
but the instance is created using C-style (which is used always for creating instances in Oxygene)
a := new Integer[42];
Due to type inference you could of course omit the declaration.
In addition to these unbound array, Oxygene offers the possibility to use bound arrays. Bound arrays can have both the lower and upper bound defined (then they're of fixed size) or only the lower bound (then they're of variable size).
type
ArrType1 = array[0..3] of Integer;
ArrType2 = array[1..] of Char;
ArrType3 = array[-2..2] of String;
//...
var a := new ArrType2(10);
For fixed size arrays you don't need to create the instance. As you can see, the lower bound is allowed to be negative.
You can use char and enums as array indexes, too:
var
a : Array[MyEnum] of Integer;
b : Array[Char] of Byte;
Multidimensional arrays can be declared (or type inferred) like this:
var a := new Integer[3,4];
var b : Array[0..,0..] of Integer;
Inline array constants can be used like this:
listBox1.Items.AddRange([3,4,5]);
var x : array of Integer := [1,2,3];
var b : array[0..,0..] of Integer := [[3,3,3],[4,5,6]];
Classes
A class is a data type that can contain fields, properties, methods, indexers and events. Also nested types can be declared inside a class.
Classes can be sealed
(cannot be inherited), abstract
or static
. Classes can be declared as partial
, so that the class declaration can span multiple files (but not multiple assemblies).
Classes can inherit other classes and implement interfaces.
Example of a class delcaration in Oxygene:
type
MyClass = public class(ParentClass, ISampleInterface) //inherits ParentClass, implement ISampleInterface
protected
fFoo : Integer; //field
method GetList(index : Integer) : String;
method SetList(index : Integer; value : String);
public
method DoSomething; //method
property Foo : Integer read fFoo; //read-only property
property List[index : Integer] : String read GetList write SetList; default; //(default) indexer
end;
SubClass nested in MyClass = protected class //Class nested in MyClass
event SomethingHappens : MyEvent;
end;
Interfaces
Interfaces are very important concept in the .NET world, the framework itself makes heavy use of them. Interfaces are the specification of a small set of methods, properties and events a class has to implement when implementing the interface. For example contains the interface IEnumerable<T>
specifies the GetEnumerator
method which is used to iterate over sequences.
Interfaces are declared just like classes:
type
MyInterface = public interface
method MakeItSo : IEnumerable;
property Bar : String read write;
end;
Please notice, that for properties the getter and setter are not explicitly specified.
Delegates
Delegates define signatures for methods, so that these methods can be passed in parameters (e.g. callbacks) or stored in variables, etc. They're the type-safe NET-equivalent to function pointers. They're also used in events.
To define a delegate one uses the method
or delegate
keyword. function
and procedure
work, too, but are deprecated.
type
SomeCallback = public method(aParam : Integer) : Boolean;
SomeEventHandler = public delegate(sender : Object; e : SomeEventArgs);
Delegates can be invoked by using there Invoke
or BeginInvoke
method, or by just calling them like methods:
var a : SomeCallback := @anAppropriateMethod;
a(42);
As you can see, when assigning a method to a delegate, one has to use the @
operator, so the compiler knows, that one doesn't want to call the method but just assign it.
Oxygene can create anonymous delegates, so for example you can pass methods to the Invoke
method of a control without declaring the delegate:
method MainForm.MainForm_Load(sender: System.Object; e: System.EventArgs);
begin
Invoke(@DoSomething);
end;
An anonymous delegate with the siganture of the method DoSomething
will be created by the compiler.
This can be combined with anonymous methods:
method MainForm.MainForm_Load(sender: System.Object; e: System.EventArgs);
begin
Invoke(method; begin
MessageBOx.Show('foo');
end);
end;
Oxygene supports polymorphic delegates, which means, that delegates which have parameters of descending types are assignment compatible. Assume two classes MyClass
and MyClassEx = class(MyClass)
, then in the following code BlubbEx
is assignment compatible to Blubb
.
type
delegate Blubb(sender : Object; m : MyClass);
delegate BlubbEx(sender : Object; mx : MyClassEx);
Object Oriented Programming
Oxygene is an object-oriented language, which means it uses classes, which can hold data and execute code, to design programs. Classes are "prototypes" for objects, like the idea of an apple is the prototype for the apple you can actually buy in the shop. You know, that an apple has a color and you know, that it can be peeled: that are is data and executable "code" for the apple class.
Methods
Methods are declared in the interface section and implemented in the implementation section of a file:
interface
type
MyClass = public class
public
method DoSomething(aParam : Integer);
end;
implementation
method MyClass.DoSomething(aParam : Integer);
begin
end;
When using class method
instead of method
, the method will be static, meaning it's called on the class itself and not on one instance of that class. One can only access other class members, not instance members from this method.
Method Parameters
Parameters are declared both in the interface and implementation section. If more than one parameter in a row has the same type, they can be defined with specifying the type only once:
param1, param2 : Integer
Parameters can be provided with a default value, which is used when the parameter isn't passed to the method when calling it:
method Foo(HasToBePassed : String; Bar : Integer := 42);
The default value is only defined in the interface section!
When declaring an array-parameter with the params
keyword, the array can be passed as sequence of parameters when calling the method:
method MethodWithManyParameters(params manyParameters : Array of Integer);
// ...
MethodWithManyParameters(1,2,42,34,27);
Modifiers
Modifiers are keywords placed behind the method declaration. They change the behaviour of the method they're applied to. The modifier is not repeated in the implementation section.
There're the following modifiers for methods:
Oxygene Modifiers for Methods
empty
The empty
modifier tells the compiler, that actually no implementation for this method exists. Therefore no method stub in the implementation section is needed. method DoNothing; empty;
virtual
Virtual methods can be overridden in descendant classes.
abstract
The abstract
modifier creates an abstract method, which is a method that must be implemented in a derived class. Abstract classes are automatically virtual.
override
In a derived class a method with the override
modifier overrides a virtual method of the base class.
reintroduce
Using the reintroduce
modifier will tell the compiler, that you really want to re-implement a base classes virtual method.
final
When a virtual method is marked as final in a descendant class, descendants of that class can no longer override the method.
locked
Thread-safe methods can be created using the locked
modifier. By default, locking works an the entire instance (i.e. self
), but one can specify the field to lock on: method LockedMethod; locked on fAField;
;
async
Additionally one can create asynchronous methods with the async
modifier. This means, a call to a method marked with this modifier will immediately return and the code of the method will be executed in a separate thread. The following will output "First" and after half a second the numbers 0 .. 10:
MyClass = public class
public
method DoSomething; async;
end;
implementation
class method ConsoleApp.Main;
begin
var mc := new MyClass;
mc.DoSomething;
Console.WriteLine('First!');
Console.ReadLine;
end;
method MyClass.DoSomething;
begin
Thread.Sleep(500);
for i : Integer := 0 to 10 do
Console.WriteLine(i);
end;
external
Methods from unmanaged libraries (e.g. the Windows-API) can used when a method is declared with the external
modifier and the DllImportAttribute
[9].
unsafe
Methods marked as unsafe
make it possible to use unmanaged code, which for example makes use of pointers.
implements
With implements
one can specify which method of an interface a method implements: method CompTo(other: MyClass): System.Int32; public implements IComparable<MyClass>.CompareTo;
The public
visibility modifier can be omitted, then the implementation is private (the class has to be explicetly cast into the interface to access the method).
iterator
An iterator (in .NET represented by the IEnumerator
interface) is an object, which has a method that will return a new element of a sequence every time this method is called. This can be used in a for
-each
-loop. When giving a method the iterator
modifier, the compiler will create such an iterator object from the method. Inside the method you have to use yield
to tell the compiler which elements the sequence should consist of:
type
MyClass = public class
public
method GetEvenNumbers: sequence of Int32; iterator;
end;
// ...
method MyClass.GetEvenNumbers: sequence of Int32;
begin
for i: Int32 := 0 to 100 step 2 do
yield i;
end;
partial
Partial methods can be declared as empty methods in one part of a partial class (i.e. they have the modifiers partial; empty;
) and implemented in another part of the partial class. If they are not implemented in another part, all calls to the method will be removed at compile time.
Anonymous Methods
Anonymous methods are implemented inside other methods. They are not accessible outside of the method unless stored inside a delegate field. Anonymous methods can use the local variables of the method they're implemented in and the fields of the class they belong to.
Anonymous methods are especially useful when working with code that is supposed to be executed in an GUI thread, which is done in .NET by passing a method do the Invoke
method (Control.Invoke
in WinForms, Dispatcher.Invoke
in WPF):
method Window1.PredictNearFuture; //declared as async in the interface
begin
// ... Calculate result here, store in variable "theFuture"
Dispatcher.Invoke(DispatcherPriority.ApplicationIdle, method; begin
theFutureTextBox.Text := theFuture;
end);
end;
Anonymous methods can have parameters, too:
method Window1.PredictNearFuture; //declared as async in the interface
begin
// ... Calculate result here, store in variable "theFuture"
Dispatcher.Invoke(DispatcherPriority.ApplicationIdle, method(aFuture : String); begin
theFutureTextBox.Text := aFuture ;
end, theFuture);
end;
Both source codes use anonymous delegates.
Extension Methods
Extension methods are class methods that are treated in a special way by the editor (especially IntelliSense) and the compiler. They're defined and implemented in one class, but are called on another class (at least it looks like if they were).
For example, if you want to define a method Implode
, which creates a string representation of a sequence, you can create a class and such a method in it:
MyClass = public class
public
class method Implode<T> (aSequence : IEnumerable<T>) : String;
end;
You'd call this method like this: MyClass.Implode(someSequence)
With extension methods, you can do it much more elegant. Class and method are decorated with the appropriate attribute to turn the method into an extension method:
[System.Runtime.CompilerServices.Extension] //namespace can be omitted
MyClass = public class
public
[System.Runtime.CompilerServices.Extension]
class method Implode<T> (aSequence : IEnumerable<T>) : String;
end;
No you can call the method directly on any sequence: someSequence.Implode
IntelliSense will show the method on any sequence, too.
At compile time, the method call will be re-written to the "normal" call to MyClass.Implode
.
The Extension
attribute is defined in the System.Core.dll starting with version 3.5 of the .NET framework. If you don't want to use that version of the framework, you can define the attribute for yourself (in the appropriate namespace) or use Mono's System.Core.dll.
Extension methods are a very important part of the implementation of LINQ.
Fields
Fields store data and can be instance or class members. They should only be used internally, to make a value public, use properties.
MyClass = public class
fInstanceField : String;
class var fClassField : Integer;
end;
Fields can be marked as read-only (see sample code below), which means that their value can only be assigned in the (class) constructor. In contrast to constants a field's value will be evaluated at runtime and therefore can be calculated using complex expression.
Initial values of a field can be defined together with the declaration:
fStartTimeAsString : String := DateTime.Now.ToString; readonly;
The compiler will put these initializations into the constructor, normally before calling the inhertied constructor[10].
Delegating Interface Implementation
Fields can be used to delegate the implemenation of an interface, if the type they're of implements this interface:
Implementor = public class(IMyInterface)
// ... implement interface ...
end;
MyClass = public class(IMyInterface)
fSomeImplementor : Implementor; public implements IMyInterface; //takes care of implementing the interface
end;
In this example the compiler will create public methods and properties in MyClass
, which call the methods / properties of fSomeImplementor
, to implement the members of IMyInterface. This can be used to provide mixin-like functionality[11].
Properties
Properties are an abstraction of values describing the object. They often provide controlled access to the value of a field. Properties have a setter and a getter, which are methods called when someone sets / gets the value of the property.
type
MyClass = public class
protected
fStringField : String;
method GetIntField : Integer;
method SetIntField(value : Integer);
public
property LonelyProperty : Boolean;
property StringProperty : String read fStringField write fStringField;
property IntProperty : Integer read GetIntField write SetIntField;
end;
In the above example three properties are defined. For the first one, the compiler will generate a field implictely and delegate the property's access to this field. For the second property, methods that read and write the value of property from / to the specified field are generated. The field for read and write and access do not have to be the same. Regarding the third property, read and write access are delegated the appropriate methods.
Using Inline Expression one can save one or another method:
type
Circle = public class
public
property Radius : Double := 1.0;
property Area : Double read Math.Pi * Radius * Radius; //inline expression
end;
The example also shows, that the value of properties with implicit fields can be initialized in the interface section.
There can be different visibility levels for read and write access, e.g. using protected write
instead of just write
.
Properties can be made virtual or abstract by applying the appropriate modifier (virtual
or abstract
).
Property Notification
Property notification is used mainly for data binding, when the GUI has to know when the value of a property changes. The .NET framework provides the interfaces INotifyPropertyChanged
and INotifyPropertyChanging
(in .NET 3.5) for this purpose. These interfaces define events which have to be fired when a property is changed / was changed.
Oxygene provides the notify
modifier, which can be used on properties. If this modifier is used, the compiler will add the interfaces to the class, implement them and create code to raise the events when the property changes / was changed.
property Foo : String read fFoo write SetFoo; notify;
property Bar : String; notfiy 'Blubb'; //will notify that property "Blubb" was changed instead of "Bar"
As you can see, the modifier can be used on properties which have a setter method. The code to raise the events will then be added to this method during compile time.
Indexers
Indexers are properties that can be accessed as if they were arrays. In Oxygene a class can have an arbitrary number of indexer properties, but only one index property can be the default indexer (which is defined by applying the default
modifier).
type
MyClass = public class
private
method GetCell(x, y : Integer): Integer;
method SetCell(x, y : Integer; value: Integer);
method GetColumn(x : Integer) : Array of Integer;
public
property Cell[x, y : Integer] : Integer read GetCell write SetCell; default;
property Column[x : Integer] : Array of Integer read GetColumn;
end;
// ...
var aMyClass := new MyClass();
aMyClass[0,0] := 5;
var aCol := aMyClass.Column[3];
Indexers cannot have implicit getters or setters, these have to be methods or inline expression.
Indexers with the same name can be overloaded by their parameter type and parameter count.
Events
An event is a member of a class or record that others can add delegates as event handlers to, which will be called, when the event is fired. Delegates can also be removed from the event.
In Oxygene one can define an add
and a remove
method or just which delegate field should be used for holding the event handlers.
Additionally the compiler can generate a "raise method", so that events can be raised from external classes.
type
MyClass = public class
private
fMember : SomeEventHandler; //field for holding event handlers
method MethodForAdding(param : SomeEventHandler);
method MethodForRemoving(param : SomeEventHandler);
public
event SimplestEvent : SomeEventHandler; //simplest declaration, compiler does the rest
event SomeEvent : SomeEventHandler delegate fMember; //event handlers will be stored in fMember
event SomeOtherEvent : SomeEventHandler add MethodForAdding remove MethodForRemoving; //methods will be used for adding and
//removing event handlers
event YetAnotherEvent : SomeEventHandler raise; //method for external raising of the event will be added
end;
add
, remove
and raise
can be decorated with a visibility modifier (e.g. protected raise
) to limit their visibility.
Event handlers are added and removed using the +=
operator and -=
operator respectively.
Parallel Programming
The goal of parallel programming is to use all cores or processors of a computer to improve performance. To reach this goal, tasks have to be distributed among several threads. The .NET framework's ThreadPool
class offered a way to efficiently work with several threads. The Task Parallel Library (TPL) was introduced in .NET 4.0 to provide more features for parallel programming. Oxygene provides language-level support for some of these features.
Asynchronous Statements
Statements starting with the async
keyword will be executed in a separate thread. The following sourcecode will print "foo", then "bar".
async begin
Thread.Sleep(1000);
Console.WriteLine('bar');
end;
Console.WriteLine('foo');
Asynchronous statements can be assigned to variables, which is useful if you have to wait for an asynchronous statement to finish at one point in your code. The following code will print "bar", then "foo".
var bar := async begin
Thread.Sleep(1000);
Console.WriteLine('bar');
end;
bar; //will wait until "bar" finishes
Console.WriteLine('foo');
If the TPL is available it will be used to implement this feature, otherwise the ThreadPool
class is used.
(Asynchronous) Futures
Futures are not limited to the use in parallel programming, but are most useful when used as asynchronous futures and are therefore described here.
A future is a value that will be calculated at one point in the future. So when accessing it, it may or may not have been calculated already. If it wasn't calculated already when accessing it the first time, the code will wait until the value is available.
The value of a non-asynchronous future will be calculated synchronously on the first access, the value of an asynchronous future will be calculated in the background and when it is accessed the first time, the code will wait for the calculation to finish.
var aFutInt : future Int32 := 42 + 23; //will be calculated when aFutInt is accessed the first time
var aSyncFutInt : future Int32 := async LongCalculation + AnotherLongCalculation; //calculation starts now and will be finished on first access
Console.WriteLine(aFutInt); //starts calculation of aFutInt and waits for it
Console.WriteLine(aSyncFutInt); //waits for the already running calculation of aSyncFutInt
You can use type inference for asynchronous futures, so you don't need to declare them as "future T" explicitly.
Similar to the language support for nullables, futures can be used like "normal" values in many cases in Oxygene.
Asynchronous Statements are typeless asynchronous futures.
Parallel Loops
Parallel loops perform the operations for different iterations in several threads (though normally there won't be as many threads as iterations). The entire loop will only finish when all the threads are finished.
for parallel i : Integer := 0 to 100 do
CalcSomethingBig(i);
Console.WriteLine('Finished');
The 100 calls to CalcSomethingBig
will each be executed asynchronously, but "Finished" will be printed out only after all of them have finished.
Important: There is no guaranteed order in which the calls will be executed! That's also the reason, why downto
cannot be used with a parallel loop.
The parallel
keyword can be used in combination with for each
, too.
Parallel loops need the TPL and won't work with only the ThreadPool
class available.
LINQ and Sequences
"LINQ" stands for "language integrated query", that are sql-like expressions to work both with "normal" data collections (arrays, lists, ...) and database tables. These queries are not written as strings, like one knows from PHP, but are integrated into the language, providing type safety, compiler checks and IntelliSense. LINQ statements are another syntax for the extension methods provided by the System.Linq
namespace, therefore they depend on these methods.
Following is a sample statement:
var u := from u in lUsers
where u.Age = 35
order by u.Name;
This is equivalent to:
var u := lUsers.Where(u -> u.Age = 35).OrderBy(u -> u.Name);
The following keywords can be used in LINQ queries: asc
, desc
, distinct
, equals
, from
, group by
, into
, join
, on
, order by
, reverse
, select
, skip
, take
.
The compiler can emit LINQ queries as expression trees, which can be executed on a database server[12]. This means, that not the entire dataset is fetched from the database and then processes on the client, but the processing is done on the server and only the result is transferred to the client.
Sequences
Sequences represent a collection of data without implying the way this data is stored. A sequence of integer
can be an array or a list, or some other collection type. It also can be a type that retrieves new data while the sequence is accessed (e.g. a type fetching bunches of data from a web service).
Sequences are declared as sequence of T
and then instantiated by using a compatible collection type (e.g. List<T>
):
var cities : sequence of String := new List<string>;
cities := ['Düsseldorf', 'Köln']; //replaces the List<string> with an array of string
Sequences can be used in for each
loops or in LINQ statements. When declared as queryable sequence
, the LINQ statement in which the sequence is used will be turned into an expression tree.
The "+" operator is supported for sequences and will be realized by using the Concat
extension method from the System.Linq
namespace.
In iterators iterations can be delegated to sequences by simply yielding the sequence.
Language Features
New Language Features in Version 3.0
Parallel Programming
Oxygene 3.0 introduces a wide range of language concepts that push the envelope for parallel programming and enable developers to create applications that seamlessly scale up for multi-core and many-core systems. This includes support for Futures, Parallel Loops, Asynchronous Statements, an improved locked directive, and more.
Property Notifications
Native language support for property notifications makes it easy to develop solutions that follow the Model/View/Controller design pattern or generally react in well-defined ways to property changes.
Nullable Expressions
Oxygene's Expression syntax has been expanded to provide full support for nullable types in arithmetic and other expressions, making the language integration of nullables even more seamless than in 'Joyride'. Improvements have also been made for casting between compatible nullable types, such as assigning a nullbale Int32 to a nullable Int64, etc.
New Language Features in Version 2.0 ('Joyride')
Sequences and Query Expressions
Query Expressions, also known as "Language Integrated Query", or LINQ for short, are a powerful new language feature that allows you to combine the querying capabilities of database languages such as SQL and apply it to any type of data, natively within the Oxygene language.
Queries can be written to work on any type of structured collection, from simple lists and arrays to database tables and other data structures. Combined with the DLinq and XLinq libraries provided by Microsoft as part of the .NET 3.5 runtime, the feature can be used to efficiently query data from database sources and XML documents, without actually retrieving the entire set of data into memory.
var u := from u in lUsers
where u.Age = 35
order by u.Name;
Lambda Expressions
Mostly used alongside LINQ and query support, lambda expressions provide a unique syntax for passing dynamic expressions as part of method parameters.
var lFiltered := lUsers.Where(u -> u.Age = 35);
Anonymous Types
Once again mostly used in LINQ expressions, anonymous types allow you quickly declare unnamed classes within a method body, to group related values in a common entity.
var lUser := new class(Name := 'Peter'; Age := 49);
Console.WriteLine(lUser.Name+' is '+lUser.Age+' years old');
Partial Methods
New Partial Method support allows you to define a stub for a method in one part of a class, allowing an optional implementation to be provided by another partial. If no implementation is provided, the method and any calls to it will be omitted from the generated assembly.
Partial Methods make it easy for auto-generated code to define methods that can be implemented by the user. This is used extensively in our new Cocoa# support, as well as upcoming support for LINQ to SQL.
Enhanced Nullable Types
'Joyride' enhanced support for nullable types, making them a full language feature that fits in neatly with the other types rather than having nullable types feel like a runtime trick. For example, variables defined as nullable Int32 allow full access to members of Int32 and behave like a true Int32 in every sense - with the addition of allowing nil values.
In combination with the new ":" operator (described below) and the newly introduced ValueOrDefault() helper function, nullable types are now easier to use than ever, and feel more natural than in any other .NET language.
Extension Methods
Extension methods are a feature introduced by Microsoft in the .NET 3.5 runtime to support LINQ, but can be used in Oxygene in a wide variety of scenarios and on all framework versions.
Simply put, Extension Methods are methods declared in a static class that extend an existing class or interface, and can be invoked on a variable of that type. For example the Where extension method provided by .NET 3.5 extends IEnumerable<T>, and thus can be used on any sequence of objects to filter the collection on an arbitrary condition, even though IEnumerable<T> does not provide a Where member:
var lUsers := array of User;
//...
var lPauls := lUsers.Where(u -> u.Name = 'Paul');
Anonymous Methods
Anonymous methods make it possible to specify code assigned to event handlers or passed to delegates right within the body of another method. They not only allow you to skip manually declaring a method within the class, but they also seamlessly allow access to any local variables available within the context where the anonymous method is written.
method SetEventHandler;
begin
var lNewCaption := 'Clicked!';
Button1.Click += method(Sender: Object; ea: EventArgs); begin
Button1.Text := lNewCaption
MessageBox.Show('the button was clicked.');
end;
end;
Colon ':' Operator
'Joyride' introduces a new operator that can be used anyplace the familiar "." can is used, be it to access a method, properties or other members of a type. Different from the "." operator, which requires the value on its left to be assigned (and will usually throw a NullReferenceException if it is not), the new ":" operator will allow to call members on any value, including nil. If the expression on the left of the colon is nil, the entire expression will automatically short-circuit to return nil as well.
This makes it very easy to access nested members in object hierarchies, when multiple if assigned() checks would otherwise be needed. (BLOG)
var lGreatGrandDadsName := lUser:Parent:Parent:Parent:Name;
'Params' Keyword
The params keyword makes it easy to define methods that take a variable number of arguments. By closing the list of parameters with an array parameter prefixed with the params keyword, you can enable callers of your method to pass variable numbers of elements to method calls, which will automatically be converted into an array. This makes it easier to call the method, especially from languages like C#, where constructing an inline array is long and unwieldy.
method Format(aFormat: string: params values: array of Int32);
//...
Format('...', 1, 2, 3, 5, 27);
'implies' Operator
The new implies operator was designed specifically for require/ensure clauses and class invariants, but can also be used elsewhere in code. Similar to and or, it combines two boolean expressions; the difference is that with implies, the second expression is only evaluated if the first/left expression is true. if the first expression is false, the entire expression will be considered true.
ensure
HasDriversLicense implies Age >= 16;
Type Inference for 'for each' Loops on Generic Sequences
For 'Joyride', for each loops have been improved to automatically infer the type of the loop variable when working on a generic IEnumerable<T> or other strongly-typed sequence, avoiding the need to manually specify the type name. As a side effect, for each loops will now always implicitly define their loop variable.
var lUsers: array of Users;
for each u in Users do
Console.WriteLine(u.Name); // Compiler knows "u" is a "User"
For non-generic enumerations (IEnumerable), a explicit type declaration will be required inside the for each loop; the compiler will not automatically infer to use System.Object.
'index' Operator for 'for each' Loops
The Syntax for each loops has been expanded in 'Joyride' to allow for an optional index variable to be defined, which will count from 0 through the number of elements looped. This is helpful in scenarios where the number of elements processed is needed as part of the loop, be it to access a separate collection by index, or to use different code to handle the first or even/odd elements.
When using for each matching or other mechanisms (such as LINQ) to filter down the collection, the index will only count those elements that actually execute the loop.
for each u in Users index i do begin
if i > 0 then Console.Write(';');
Console.Write(u.Name);
end;
New Language Features in Version 1.5 ('Floorshow')
Generic Methods
Use Generics to implement strongly typed methods with and parameterized types (.NET 2.0 only).
Iterators
Easily implement collections and enumerable classes using iterators for both .NET 1.1 and 2.0 using the new 'iterator' directive and the 'yield' keyword.
method CountTo100: Int32; iterator;
begin
for i: Int32 := 0 to 100 do yield i;
end;
Nullable Types
Avoid boxing by using new nullable value types on the .NET 2.0 framework. Support for nullable types has been vastly enhanced for 'Joyride' (see above).
var i := nullable Int32;
if assigned(i) then i.CompareTo(5);
Nested Types
Define and implement nested types using Oxygene's new and intuitive 'nested in' syntax
type
HelperClass nested in MainClass = class //...
Dual-visibility for Properties
Define properties with different visibility levels for getter and setter, for example allowing public reading and protected writing of properties:
property Count: Int32 read fCount protected write SetCount;
Extended Constructor Calls
Create objects and initialize properties in a single statement
var b := new Button(Width := 150; Height := 25);
Fixed Size Buffers
Use Fixed Size Buffers to declare efficient inline arrays inside your records, in "unsafe" code.
New Language Features over Object Pascal in Version 1.0
Generic Types
Use Generics to implement strongly typed containers and parameterized types.
Class Contracts
Oxygene is the first mainstream .NET language to provide native support for Design By Contract like constructs, with pre-conditions, post-conditions and invariants.
Namespace Support
Namespaces are one of the great basic concepts of the .NET framework that most developers take for granted. Oxygene provides three basic features that allow developers to work with namespaces.
Virtual Properties
Virtual Properties and Events allow you to more easily define abstract classes and interfaces, or overwrite existing framework interfaces that contain properties.
Enhanced Events Support
Oxygene introduces a new syntax for defining and working with events to the Object Pascal language.
Asynchronous Methods and Thread Synchronization
Easily write multi-threaded applications using Oxygenes' async keyword and asynchronous methods. Use the locked and locking keywords to write thread-safe applications.
Partial Classes
The only .NET compiler to provide partial classes support for .NET 1.1.
Operator Overloading
Make your classes intuitive to use by providing custom operator overloads for common operations such as addition or subtraction.
Class References & Virtual Constructors
Easily implement the Factory Pattern or dynamically create object instances using Oxygene's Class References (Meta Classes).
Enhanced Loops
Oxygene enhances the classic for/to loop to allow inline declaration of the loop variable type. It also provides a new for each loop type for looping across .NET IEnumerable and IEnumerable<T> types, as well as an infinite loop loop.
Inline Variable Declarations and Type Inference
Declare new variables using the 'var' statement inside your method bodies to keep them with the code that uses them. Avoid retyping type names and let Oxygene infer new variable types from the assigned value.
Inline Property Readers
Use inline code to implement simple property readers, such as
property Foo: String read 'Bar';
Enhanced 'case of' and 'case type of' statements
'case' statements can use strings or other non-ordinal types, as well as the new 'case type of' statement to execute different cases depending of an object's type.
case aClassID.ToUpper of
'XYZ': result := TMyXYZClass;
'ABC': result := TMyOtherClass;
else raise new Exception('Invalid Class ID');
end;
case aClass type of
TMyXYZClass: TMyXYZClass(aClass).DoSomething;
TMyOtherClass: TMyOtherClass(aClass).DoSomethingElse;
else raise new Exception('Invalid Class Reference');
end;
Enhanced 'try/finally/except'
Combine 'finally' and 'except' to create more concise and efficient exception handling code.
Exception Filters
Previously only available in Visual Basic .NET, Exception Filters provide extended flexibility for catching exceptions over common 'try/except' blocks.
Boolean Double Comparisons
Easily compare values against boundaries with statements such as
if 0 <= x < Count then //...
Empty Methods
Quickly define class interfaces to flesh out later or empty methods to be overridden in descendant classes.
Static Classes
Implement static classes that cannot be instantiated at runtime, but provide static functionality to your project.
Enhanced 'exit' statement
Use the improved exit statement to terminate methods and set a return value in a single step.
Enhanced 'is not' and 'not in' statements
Use the new 'is not' operator to write more readable type check statements, and 'not in' for improved set handling.
Differences between native Delphi and Oxygene / Delphi Prism
Deprecated Keywords
- unit: Replaced with the namespace keyword. Since Oxygene doesn't compile per-file but per-project, it does not depend on the name of the file. Instead the unit or namespace keyword is used to denote the default namespace that all types are defined in for that file
- procedure and function: These two keywords have been replaced with the method keyword.
- overload: In Delphi Prism all methods are overloaded by default, so no special keyword is needed for this.
- .Create(): This constructor call has been replaced by the new keyword. It can still be enabled in the project options for legacy reasons.
Criticism
Some people would like to port their Win32 Delphi code to Prism as is. This is not possible because while Prism looks like Delphi there are enough changes to make it incompatible for a simple recompile.
So while the name makes you think it is just another version of Delphi that is not completely true.[13]
On top of the language differences the Visual Component Library framework is not available in Delphi Prism.[14]
This makes porting even more difficult because classic Delphi code relies heavily on the VCL.
Code examples
Hello World
namespace HelloWorld;
interface
type
HelloClass = class
public
class method Main;
end;
implementation
class method HelloClass.Main;
begin
System.Console.WriteLine('Hello World!');
end;
end.
Generic container
namespace GenericContainer;
interface
type
TestApp = class
public
class method Main;
end;
Person = class
public
property FirstName: String;
property LastName: String;
end;
implementation
uses
System.Collections.Generic;
class method TestApp.Main;
begin
var myList := new List<Person>; //type inference
myList.Add(new Person(FirstName := 'John', LastName := 'Doe'));
myList.Add(new Person(FirstName := 'Jane', LastName := 'Doe'));
myList.Add(new Person(FirstName := 'James', LastName := 'Doe'));
Console.WriteLine(myList[1].FirstName); //No casting needed
Console.ReadLine;
end;
end.
Generic method
namespace GenericMethodTest;
interface
type
GenericMethodTest = static class
public
class method Main;
private
class method Swap<T>(var left, right : T);
class method DoSwap<T>(left, right : T);
end;
implementation
class method GenericMethodTest.DoSwap<T>(left, right : T);
begin
var a := left;
var b := right;
Console.WriteLine('Type: {0}', typeof(T));
Console.WriteLine('-> a = {0}, b = {1}', a , b);
Swap<T>(var a, var b);
Console.WriteLine('-> a = {0}, b = {1}', a , b);
end;
class method GenericMethodTest.Main;
begin
var a := 23;// type inference
var b := 15;
DoSwap<Integer>(a, b); // no downcasting to Object in this method.
var aa := 'abc';// type inference
var bb := 'def';
DoSwap<String>(aa, bb); // no downcasting to Object in this method.
DoSwap(1.1, 1.2); // type inference for generic parameters
Console.ReadLine();
end;
class method GenericMethodTest.Swap<T>(var left, right : T);
begin
var temp := left;
left:= right;
right := temp;
end;
end.
Program Output:
Type: System.Int32
-> a = 23, b = 15
-> a = 15, b = 23
Type: System.String
-> a = abc, b = def
-> a = def, b = abc
Type: System.Double
-> a = 1,1, b = 1,2
-> a = 1,2, b = 1,1
---
See also
References
- ^ [1] Embarcadero Delphi Prism page, at the bottom can be seen that they license the Oxygene Compiler.
- ^ http://prismwiki.codegear.com/en/Operator_Overloading
- ^ http://prismwiki.codegear.com/en/Built-In_Types
- ^ http://prismwiki.codegear.com/en/Provide_Mixin-like_functionality
- ^ http://prismwiki.codegear.com/en/Special_Identifier_Escape_Syntax
- ^ http://prismwiki.codegear.com/en/Operator_Overloading
- ^ http://prismwiki.codegear.com/en/Built-In_Types
- ^ http://prismwiki.codegear.com/en/Nullable_Types_in_Expressions
- ^ http://prismwiki.codegear.com/en/DllImportAttribute
- ^ http://prismwiki.codegear.com/en/Fields#Field_Initialization
- ^ http://prismwiki.codegear.com/en/Provide_Mixin-like_functionality
- ^ http://prismwiki.codegear.com/en/Expression_Trees
- ^ [2] A Stack overflow discussion where people remark that Delphi Prism is not Delphi Win32.
- ^ [3] Delphi Prism 2010 review where they state in the third paragraph that VCL.net is not available.
External links
- Articles with topics of unclear notability from October 2009
- Articles with a promotional tone from October 2009
- Articles to be merged from December 2009
- Object-oriented programming languages
- Class-based programming languages
- .NET programming languages
- Pascal compilers
- Mono project
- Pascal programming language family