|
|
|
|
The previous sections focused on the "big picture" features of Oxygene, the language basics, and major features that we classified into modern, special and awesome.
But there are also a lot of little things that make Oxygene great — small language enhancements that may not seem like much, but really change how you write code.
Let's have a look at some of those.
In Oxygene, like in many of the languages it was influenced by, the "." operator is used to call members on a class or object, such as
var x := y.SomeProperty;
This "dereferences" the object contained in "y", calls (in this case) the property getter and returns its value. If "y" happens to be unassigned (i.e. "nil"), an exception is thrown.
The ":" operator works in much the same way, but instead of throwing an exception on an unassigned object, the result will simply be nil. For developers coming from Objective-C, this will be familiar, as that is how Objective-C method calls using the [] syntax work, too. (In "Nougat", the ":" operator will be the equivalent to the usual Objective-C method calls, when porting code).
This works even for simple types, automatically converting the result to a nullable. For example, given:
var x := someString:Length;
the variable "x" will be of type "nullable Int32" and it will contain nil, if someString itself is nil.
Where ":" really shines is when accessing properties in a chain, where any element might be nil. For example, the following code:
var y := MyForm:OkButton:Caption:Length;
will run without error, and return nil if any of the objects in the chain are nil — the form, the button or its caption.
Oxygene provides two nice extensions when comparing values via the equal/greater/lesser operators, and combinations thereof:
Double Boolean Comparisons let you easily encode two checks in one, for example to check if x is between 6 and 10:
if 5 < x <= 10 then begin
In addition, Oxygene supports the proper unicode operators =, = and ? in addition to the two-character combinations of <=, >= and <> — making for nicer and more readable code.
"for each" loops are handy to enumerate over a sequence of elements, and not only save the hassle of dealing with the indexer in a regular for loop, but also are more readable, and work on more types (especially non-indexed collections).
But sometimes it is still useful to know the index within the loop — for example to treat the first iteration different, or perform different output for even vs. odd elements. For this, Oxygene for loops provide an optional index syntax, where
for each s in myStringList index i do begin
…
end;
will automatically give you access to an Int32 variable "i" that keeps count as your loop progresses.
Delphi introduced the "is" and "in" operators for checking class ancestry and membership of a set, respectively. These are great and useful, but awkward to use when checking for a negatory result:
if not (X is String) then …
if not (5 in [1,2,3]) then …
Oxygene alleviates this and makes these checks easier to write and read, by providing explicit "is not" and "not in" operators, making for a more natural and readable flow of code:
if X is not String then …
if 5 not in [1,2,3] then …
Compilers are pretty smart, and can usually figure out of what type a given expression is. So why should you have to repeat yourself and write, say:
var x: List<String> := new List<String>;
when the compiler can do the work for you? Oxygene supports type inference, so in the vast majority of cases, when you are declaring and pre-initializing a variable, you do not need to specify the type:
var x := new List<String>;
and Oxygene will figure it out for you.
Sometimes you want to declare a property that is read-only to the outside world. In most languages, that means marking the property read-only, and using other means throughout your own code to set or change the value. That's ugly, and even worse, error prone, as soon as inheritance comes into play.
Oxygene lets you declare a different visibility for the getter and setter of a property; this way you can — for example — declare a property that can be read publicly, but only written to protected (i.e. by your own class and its descendants).
"implies" is a nifty little binary operator that comes in handy, especially in Class Contracts. It takes two boolean expressions; if the first expression is false, it returns true, but if it is true, it evades the second expression instead.
That seems awkward when explained like that, but consider the following example:
ensure
result implies aOutValue > 0;
What this means, essentially, is that if the method result is true, we want to ensure that aOutValue is greater than 0. But if the result is not false, then we simply don't care about the second expression.
This kind of logic would be terribly awkward to express with regular and/or operators.
Everyone is familiar with regular "if" statements — a condition is checked, and one or the other statement is executed — and "for" loops.
As of Oxygene 4, "if", "case" and "for" can also be used as expressions — that is, as statements that have a value and can be used inside other expressions:
Console.WriteLine(if result then 'yes' else 'no');
uses an "if" expression of type String to print out "yes" or "no" depending on the value of the boolean "result" (these kinds of expressions come in extremely handy in ASP.NET). The same applies to "case" statements.
"for" loop expressions actually return an "inline iterator", so you could write something along the lines of
var numbers := for x := 0 to 1000 do yield x*5;
and "numbers" would now be a "sequence of Int32" that can contain the numbers between 0 and 5000, in steps of 5.
If you are often dealing with large constant numbers in your code, Oxygene 5.2's new number literals will come in handy, as you can use spacing between groups of digits to keep the number readable — usually in groups of 3 for decimal, and groups of four for hexadecimal (but you can group them anyway you please):
c = 299 792 458;
max32 = $ffff ffff;