Oxygene (programming language)
From Seo Wiki - Search Engine Optimization and Programming Languages
This article may not meet the general notability guideline. Please help to establish notability by adding reliable, secondary sources about the topic. If notability cannot be established, the article is likely to be merged, redirected, or deleted. (October 2009) |
This article is written like an advertisement. Please help rewrite this article from a neutral point of view. For blatant advertising that would require a fundamental rewrite to become encyclopedic, use {{db-spam}} to mark for speedy deletion. (October 2009) |
It has been suggested that this article or section be merged into Delphi Prism. (Discuss) |
File:Chrome-128.png | |
Developer | RemObjects Software |
---|---|
Stable release | 3.0.21 (August 29, 2009 ) |
Influenced by | Object Pascal, C# |
OS | Common Language Infrastructure |
License | Commercial |
Website | Oxygene Homepage |
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 special, 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.
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 there 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
Identifiers in Oxygene consist of unicode letters. It must not start with a digit, but may contain them. The underscore "_" is allowed at the beginning and inside an indentifier. 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 idenitifer-name[2].
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 delcare and initialize a variable without explicetly 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 deinfition 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 ".<code>" 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
<code>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[3].
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 of 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[4], 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 SecretEnuerable = 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 wether 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[5], 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 explicetly specified.
Delegates
Delegates define sigantures 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 delegete 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 [6].
|
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 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.
|
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[7].
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[8].
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.
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.[9]
On top of the language differences the Visual Component Library framework is not available in Delphi Prism.[10] 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
- C Sharp (programming language)
- Delphi programming language
- Free Pascal
- Eiffel (programming language)
- Java (programming language)
References
- ↑ [1] Embarcadero Delphi Prism page, at the bottom can be seen that they license the Oxygene Compiler.
- ↑ 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
- ↑ [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
- Oxygene Chrome Wiki
- Oxygene Homepage
- Oxygene / Chrome Forum on C-Sharp-Forum.de Template:Ger
- Bitwise Magazine Interview with Oxygene Chief Architect
- Bitwise Magazine review of Oxygene (then Chrome) 1.5
|
|
Template:RemObjectsko:크롬 (프로그래밍 언어) ru:Oxygene (язык программирования)
↓