- Oxygene (programming language)
-
Oxygene Developer RemObjects Software Stable release 3.0.21 (August 29, 2009 )Influenced by Object Pascal, C# Platform Common Language Infrastructure License Commercial Website remobjects.com/oxygene Oxygene (formerly known as Chrome) is a programming language developed by RemObjects Software for Microsoft's 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/2010. Starting the Fall of 2011, Oxygene is adds support for the Java and Android runtimes, as well. The Embarcadero Prism product continues to focus on .NET, with Java support being available as a separate product from RemObjects Software. Both products/platforms share the same core compiler and development environment.
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: those are the 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
Types
As a .NET language, Oxygene uses the .NET type system: There are 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 asInteger
andBoolean
(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 theinternal
visibility in C#. The other possible type visibility ispublic
.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 theGetEnumerator
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
andMyClassEx = class(MyClass)
, then in the following codeBlubbEx
is assignment compatible toBlubb
.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 offSomeImplementor
, 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 a 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
andINotifyPropertyChanging
(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; notify '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.
Oxygene language syntax
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 binaryand
. 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
andin
thenot
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 themod
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 calls to 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 conditional 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
andend
. Variables declared in a code block are not visible / usable outside of it.Conditional structures
if
statementA complete
if
statement in Oxygene consists of theif {condition} then {code} else {code}
The code can be one command or a block of code. When nesting
if
statements without creating separate code blocks,else
branches always belong to the next innerif
branch, which is indicated by indentation in the following code:if {condition} then if {condition} then {command} else {command};
case
statementThe
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 ordinal 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
LoopIn 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 (usingdownto
instead ofto
) and make steps larger than one (0 to 42 step 4
).repeat
-until
LoopThe
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
LoopThe
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
LoopThe
loop
loop executes its body without any condition indefinitely, until it is aborted usingbreak
(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
) LoopWith the
for each
loop one can iterate over a sequence, i.e. anything that implementsIEnumerable
and/orIEnumerable<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
orcontinue
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
andtry
-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 thefinally
block and noexcept
block is there, the exception will "bubble up" in your program, while in anexcept
block you have to use theraise;
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 exception 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 asInteger
andBoolean
(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 theinternal
visibility in C#. The other possible type visibility ispublic
.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 theflags
keyword will assign every item a value of 2i, wherei
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 anullable boolean
andvalueOrDefault
will returnfalse
, if the value is not set and its value otherwise.Although their 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 tonil
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
orstatic
. Classes can be declared aspartial
, 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 theGetEnumerator
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
ordelegate
keyword.function
andprocedure
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
orBeginInvoke
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
andMyClassEx = class(MyClass)
, then in the following codeBlubbEx
is assignment compatible toBlubb
.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 ofmethod
, 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 on 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 theDllImportAttribute
[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;
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 afor
-each
-loop. When giving a method theiterator
modifier, the compiler will create such an iterator object from the method. Inside the method you have to useyield
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 elegantly by decorating the method and dedining class 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 offSomeImplementor
, 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 justwrite
.Properties can be made virtual or abstract by applying the appropriate modifier (
virtual
orabstract
).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
andINotifyPropertyChanging
(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; notify '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 aremove
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
andraise
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 withfor 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 asqueryable 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 theSystem.Linq
namespace.In iterators iterations can be delegated to sequences by simply yielding the sequence.
Language features
New 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 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 lUsers 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 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 one 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 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 one 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 of the page an image stating it is powered by RemObjects Oxygene.
- ^ 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
- Official website
- Oxygene Chrome Wiki
- Oxygene / Chrome Forum on C-Sharp-Forum.de (German)
- Bitwise Magazine Interview with Oxygene Chief Architect
- Bitwise Magazine review of Oxygene (then Chrome) 1.5
Dialects Compilers CurrentDelphi · Delphi Prism (Oxygene) · PocketStudio · HP Pascal · IP Pascal · Prospero Pascal · C/AL · Free Pascal (Lazarus) · GNU Pascal · Turbo51 · Virtual Pascal · MIDletPascal · Pic Micro Pascal · ACKHistoricalComparison of Pascal and C · Comparison of Pascal and Delphi
Related to: Ada (2005) · ALGOL (1968).NET Framework Architecture CLI Languages Common1Other2Components ADO.NET (Entity Framework · Data Services) · ASP.NET (AJAX · MVC · Dynamic Data) · Language Integrated Query · CardSpace · ClickOnce · Communication Foundation · Dynamic Language Runtime · Forms · Presentation Foundation · Remoting · Workflow Foundation · XAML · Parallel Extensions · Managed Extensibility FrameworkOther implementations Mono · Compact Framework · XNA Framework · Micro Framework · Portable.NET · Silverlight · SSCLI · DotGNUComparisons Upcoming "Jasper" · "Roslyn"Past "Acropolis"Tools 1 Languages that are, will be, or have been included with Visual Studio or Microsoft Windows.
2 Non-academic or research languages with relatively large user-bases.Executive Team marc hoffman — Mike Orriss — Carlo KokCommercial products Data Abstract — RemObjects SDK — Hydra — Delphi PrismOpen source products Internet Pack — Pascal Script — RemObjects ScriptRelated RemObjects Software Website: www.remobjects.com Categories:- Microsoft development tools
- Object-oriented programming languages
- Class-based programming languages
- .NET programming languages
- Pascal compilers
- Mono project
- Pascal programming language family
Wikimedia Foundation. 2010.