Chapter 7
Control structures
This chapter is about the commands most programming languages have for controlling the flow of the execution. Much like C#, X# offers everything but a GoTo. The syntax is much more like it was in Visual Objects and has more similarity to Visual Basic than to C# (which I think is good).
Since every developer is familiar with the basic control structures like if, else, elseif, for, do, while, etc. I will discuss only a very few examples.
The classic If, ElseIf, EndIf command
Many C# "leave no good hair" (sorry, German language humor;) when they are confronted with nested if statements that contain a corresponding endif for each if. They prefer their curly braces. I think X# syntax makes the control flow a little better readable for "the average developer" than nested code blocks without explicit end commands - but maybe we should try to avoid such nested structures at all.
Anyway, the X# syntax of If, Else, ElseIf, and EndIf is clean and simple (or vice versa), and at least there is no Then like in Visual Basic (which could be omitted). For logical expressions, you can use either || or .or. for a logical Or and && or .And. for a logical And.
**Example 7.1: (XS_IfElseEndIf.prg) **
// A small example for if, elseif and endif
Function Start() As Void
var weekday := DateTime.Now:ToString("dddd")
var curHour := DateTime.Now:Hour
If weekday == "Samstag" && weekday == "Sonntag"
? "Enyoy our weekend"
ElseIf curHour >= 8 || curHour <= 17
? i"Today is {weekday}, so GoTo work or enjoy your lunch break"
EndIf
Looping commands
There are four looping commands in X#:
- For/Next
- Repeat/Until
- Do/While
- ForEach
Command | Definition | Condition |
---|---|---|
For/Next | Loops a given number to times | Either UpTo() or DownTo() |
Repeat/Until | Loops until a condition is fullfilled | Follows Until |
While/End While | Loops while a condition is true | Follows While |
ForEach | Iterates through an Array/Collection etc | Number of elements |
Tab 7.1: The looping commands of X#
For/Next loops as the workhorse of many applications
It's not clear who invented the classical for loop but a short investigation on the Internet (conducted by myself) revealed two little-known facts: 1) A for loop was not part of the famous FORTRAN language (invented by the also very famous John Backus from IBM) which only had/has a do loop (a wise decision) and 2) the first mentioning of a for loop goes back to "Algol 58" which (or the Algol language family to be more precisely) is considered as the first structural language at all and can be considered as the mother of all languages that are in use today (the for command was probably reinvented with BASIC in the 60s and either Thomas E. Kurtz or John G. Kemeny or both decided that a for does not make sense without a next command.).
**Example 7.2: (XS_ForLoop1.prg) **
// Classic for loop with index variable and step
Function Start() As Void
For Local i := 0 UpTo 10 Step 3
? i
Next
UpTo always assumes that the end value is higher than the start value. Otherwise, the DownTo keyword is the one to be used.
The increment step is always 1. It can be any other integer value by using the Step keyword.
**Example 7.3: (XS_ForLoop2.prg) **
// Classic for loop with a lower end value
Function Start() As Void
For Local i := 10 DownTo 1 Step 2
? i
Next
QUIZ: How many times does a loop "loop" that goes from 0 to 0 or from 1 to 1 for example? Zero times or one time? It's one time with both UpTo and DownTo both in the core and the VO dialect.
Breaking out of a for loop with exit
The break statement from C# has a completely different meaning in X#, it raises an exception (like throw would in C#). The equivalent is the exit command.
Iterating over everything (that's iterable) with ForEach
By far the most flexible loop is the ForEach loop. The only little drawback is to have a separate countervariable if needed. The loop variable is typically declared with var but it's also possible to use an explicit type. If the looping variable is an int, the variable is declared with the Local keyword and without a data type.
**Example 7.4: (XS_ForEachLoop1.prg) **
// Classic for loop with a negative step
Function Start() As Void
For Local i := 10 DownTo 1 Step 2
? i
Next
**Example 7.5: (XS_ForEachLoop2.prg) **
**Example 7.6: (XS_ForEachLoop3.prg) **
```C#
**tip**: One really good thing about *ForEach* is that when iterating over an array it doesn't matter if the index starts with 0 or 1.
### When ForEach produces warning XS9064
In some (probably rare) circumstances, the compiler might issue a warning *XS9064* combined with a note that you should not assign something to the iteration variable. Although it's scientifically proven that this programming technique has no side effects, I would not consider it a good programming practice. And a warning is a warning.
The following examples show such a case.
**Example 7.7: (XS_ForEachVarWarning.prg) **
```C#
// Example for assigning a new value to the ForEach iterator variable which results to a compiler warning
using System.Collections.Generic
Function Start() As Void
var cities := List<String>{}{"Frankfurt","Munich","Berlin","Hamburg"}
ForEach var city in cities
city := city:ToUpper()
? city
Next
Skipping iterations within a loop
A loop can either exit or just terminate the current iteration and start with the next. There are two commands: Loop and Exit. Quote from the X# help:
Loop branches control to the most recently executed FOR, FOREACH, REPEAT or DO WHILE statement.
Exit branches unconditionally from within a FOR, FOREACH, REPEAT or DO WHILE statement to the statement immediately following the corresponding END or NEXT statement.
Skip iterations inside a REPEAT/UNTIL loop
There are two keywords for terminating a loop prematurely: continue and loop.
**Example 7.8: (XS_RepeatUntilWithSkip.prg) **
// Simple exit of a loop
Function Start() As Void
var counter := 10
Repeat
counter--
var z := Random{DateTime.Now:Millisecond}:Next(1,10)
if z < 5
Loop
endif
? i"*** Counting {counter} ***"
Until counter == 0
? "*** Finito ***"
**Example 7.9: (XS_ForEachWithList.prg) **
### While loops with a continue are not a good idea
_continue_ is a well-known command in many languages like C#, Visual Basic, and Python. It is used inside a loop to start another iteration. Whereas _break_ quit the loop _continue_ is supposed to start another round and skip the rest of the "loop body". It's like a Goto but you can only go to the beginning of the loop so that the loop condition will be tested again.
What costs me some sweating in a large project is the fact that the _Continue_ command has a completely different meaning in X# and leads to a "strange" error when used inside a loop. To make a long story short: _Loop_ is the right command to start the next iteration.
**Example 7.10: (XS_WhileLoop.prg) **
```C#
// Skipping iterations with Loop (continue in C#)
Function Start() As Void
Local Counter := 1 As Int
While Counter < 10
Counter++
If Counter > 5
Loop
Endif
? i"*** Looping Nr. {Counter} ***"
End While
Fig 7.1: A 'strange' error appears when continue is used inside a loop
Endless loops
X# has no special command for producing the infamous endless loop. It's just a do loop with a True condition.
Switches
X# offers two switch commands:
- Switch Case
- Do Case
The first of the two is older and supposed to be faster because the switch condition is only evaluated once. The latter of the two is a little more flexible because each case branch has its expression. The X# help states that there is no performance difference in comparison to a "chained" if and elseif statement.
A simple switch case example
**Example 7.11: (XS_SwitchSimple1.prg) **
// A simple switch case
Function Start() as void
Local Score := "No Score" As String
? "Grade?"
var Grade := Console.Readline()
Switch Grade
Case "A"
Score := "Really superb, top notch"
Case "B"
Score := "Excellent"
Case"C"
Score := "Very good"
Case "D"
Score := "Goo but there is room for improvements"
Case "E"
Score := "Sure you can do better"
Case "F"
Score := "Don't give up, try again"
Otherwise
Score := "Unknown score"
End Switch
? i"Grade: {Grade} Score: {Score}"
A switch case example with multiple case branches
**Example 7.12: (XS_SwitchMultipleCase.prg) **
// A simple switch with multiple case branches
Function Start() As Void
Local Score := "No Score" As String
?"Grade? "
var Grade := Console.ReadLine()
Switch Grade
Case "A+"
Case "A"
Case "A-"
Score := "Really superb, top notch"
Case "B"
Case "B+"
Score := "Excellent"
Case "B-"
Case "C"
Case "C+"
Score := "Very good"
Case "D"
Score := "Good but there is room for improvements"
Case "E+"
Score := "You have passed, but sure you can do better"
Case "E"
Score := "You have nearly missed it, get better next time"
Case "F"
Score := "Don't give up, try again"
Otherwise
Score := "Unknown score"
End Switch
? i"Grade: {Grade} Score: {Score}"
Combining a switch case with a when filter
The switch statement allows multiple case branches for the same value but the duplicate expressions must be added with a when clause so that multiple values are ok.
**Example 7.13: (XS_SwitchWithWhen.prg) **
// Combining a switch case with a when filter
Enum ThreadLevel
Low
Medium
High
End Enum
Function Start() As Void
Var level := ThreadLevel.Medium
Var exerciseMode := true
Var message := ""
Switch Level
Case ThreadLevel.Low When exerciseMode == True
message := "Low danger"
Case ThreadLevel.Low
message := "Low danger but be alert"
Case ThreadLevel.Medium When ExerciseMode == True
message := "Medium danger"
Case ThreadLevel.Medium
message := "Medium danger so be aware"
Case ThreadLevel.High When ExerciseMode == True
message := "High alert but nothing to worry"
Case ThreadLevel.High
message := "High alert - evacuate immediately"
Otherwise
message := "Unknown status - we just don't know"
End Switch
? message
A simple switch with the Do Case command
A Do Case does the same as If..ElseIf..Else..EndIf but it might make the source code more readable.
**Example 7.14: (XS_DoCaseSwitch.prg) **
// A simple switch with Do Case
Function Start() As Void
Local Score := "No Score" As String
?"Grade? "
Local Grade := Console.ReadLine() As String
Do Case
Case Grade == "A"
Score := "Really superb, top notch"
Case Grade == "B"
Score := "Excellent"
Case Grade >= "C"
Score := "Good but there is room for improvements"
Case Grade == "E*"
Score := "Sure you can do better"
Otherwise
Score := "Don't give up, try again"
End Case
? Score
Using a regex as an expression in a do case statement
Time for some regex pattern-matching magic, I mean regular expressions of course. Since within a Do Case statement any kind of expression can follow the case statement it can also be a "regex".
The following example matches a list of serial numbers against the current year in the second group of three.
**Example 7.15: (XS_DoCaseWithRegex.prg) **
// Using a regex with do case
using System.Text.RegularExpressions
using System.Collections.Generic
Function Start() As Void
Var serNumbers := List<String>{}{;
"1234-2022-91bE",;
"5454-2021-A6bC",;
"4323-2022-69bD",;
"9843-2022-42bA";
}
ForEach Var serNumber In serNumbers
Do Case
Case Regex.Match(serNumber, "\d+-(\d+)-\d+"):Groups[1]:Value == DateTime.Now:ToString("yyyy")
? i"*** Match mit {serNumber}"
Otherwise
? i"!!! Kein Match mit {serNumber}"
End Case
Next
Pattern matching for a case expression
Pattern matching means much more flexible condition expressions in a switch statement. C# has pattern matching since version 8.0.
X# also offers a kind of pattern matching in a switch statement since version 2.6. I have not used it yet, so I will just give an example that Robert was so kind to send me.
**Example 7.16: (XS_CasePatternMatching.prg) **
// An example for Case pattern matching supplied by Robert van der Hulst
Function Start() As Void
Var oValues := <Object>{1,2.1,"abc", "def", TRUE, FALSE, 1.1m}
ForEach Var o in oValues
Switch o
Case i AS LONG // Pattern matching
? "Long", i
Case r8 AS REAL8 // Pattern matching
? "Real8", r8
Case s AS STRING WHEN s == "abc" // Pattern matching with filter
? "String abc", s
Case s AS STRING // Pattern matching
? "String other", s
Case l AS LOGIC WHEN l == TRUE // Pattern matching with filter
? "Logic", l
Otherwise
? o:GetType():FullName, o
End Switch
Next
A critical objection and a modern alternative to the Do Case command
Commands like Switch case and Do Case are an important part of any language, also in X#, they are helpful and well implemented. But, they are a relic from the 80s and maybe even from the 70s. Although I am by no means suggesting that X# developers should avoid them, we could and should do a little "better" in the 21st century, especially in the year 2024.
I sometimes use a dictionary of lambda expressions (anonymous methods that is) as an alternative to a typical Do case statement. The first lambda evaluates an expression, the second lambda defines an action to be taken when the expression is True.
I have to admit that it took me some time to make the following example compile but I did it with absolutely no help from the Internet (because there isn't any and because I might be some kind of genius;).
**Example 7.17: (XS_DoCaseWithDictionary.prg) **
// A dictionary as a do case alternative
// Compile with xsc /dialect:vo /r:RuntimeLibs/XSharp.rt.dll /r:RuntimeLibs/XSharp.Core.dll
using System.Collections.Generic
using System.Linq
// Delegate ScoreAction() As String
function Start() As Void
var dicAction := Dictionary<Func<String, Boolean>, Func<String>>{}
local f1 := { => "You are a genius!" } as Func<String>
local f2 := { => "Well done" } as Func<String>
local f3 := { => "Room for improvements" } as Func<String>
dicAction[{score as String => score == "A"}] := f1
dicAction[{score as String => score == "B"}] := f2
dicAction[{score as String => score == "C"}] := f3
// Enumerate the keys that means all expressions
Local ClassScores := {} As Array
AAdd(ClassScores, {"Charles","A"})
AAdd(ClassScores, {"Steve","A"})
AAdd(ClassScores, {"Bill","B"})
AAdd(ClassScores, {"Larry","C"})
AAdd(ClassScores, {"Robert","A"})
ForEach var Score in ClassScores
ForEach var EvalExpr in dicAction:Keys
If EvalExpr(Score[2])
? i"{Score[1]} - {dicAction[EvalExpr]()}"
EndIf
Next
Next
This is one of the few X# examples in this book where I have to admit that I don't know exactly why it compiles without any errors but it does. Somehow I managed to overcome error messages like "Method name needed" or "Untyped arrays are not available in the selected dialect 'Core'". Kudos to the X# developer team for making this all possible and working so smoothly and reliably.
// Instancing the dictionary and adding entries
funDic := Dictionary<String, Func<Int,Int,Int>>{}
funDic["Add"] := {op1 As Int, op2 As Int =>
Return op1 + op2
}
funDic["Sub"] := {op1 As Int, op2 As Int =>
Return op1 - op2
}
funDic["Mul"] := {op1 As Int, op2 As Int =>
Return op1 * op2
}
funDic["Div"] := {op1 As Int, op2 As Int =>
Return op1 / op2
}