Chapter 6

X# data types, variables, operators and strings

This chapter is about the syntax elements most (if not all) readers will be already familiar with. Since X# is a 100% .Net language, the data types are the same as in C#. Just with different names like Logic instead of Boolean. The familiar Usual data type is available too, but only within the VO dialect.

X# data types

Since X# is based on the CLR (Common Language Runtime) of the underlying .Net runtime it has to use the same data types. But they do not have to have the same names of course. For example, the popular Logic data type is just an alias for the CLR type boolean. And what about using Bool instead? Not possible because this is not a (native) data type.

And what about Usual? No problem, but the code has to be compiled in another dialect than Core. Then Usual can be used as usual;)

X# data type CLR data type Definition
BYTE byte An unsigned 8-bit integer.
CHAR char A 16-bit unicode.
DWORD UInt32 An unsigned 32-bit integer.
DECIMAL Decimal A 96-bit number (+ 32-bit field of flags) that allows floating point values without the usual rounding errors.
INT Int32 A signed 32-bit integer.
INT64 Int64 A signed 64-bit integer.
LOGIC Boolean An unsigned 8-bit integer.
OBJECT object The topmost reference type in the type system hierarchy of the CLR.
REAL4 Single 32-bit floating point value with a max of 9 digits after the point.
REAL8 Double 64-bit floating point value with a max of 19 digits after the point.
STRING String A reference type of 16-bit Unicode characters.
VOID Void 0
WORD UInt16 An unsigned 16-bit integer.
DATE - A usual type variant for storing a date with a day, month, and year value.
CODEBLOCK - A "survivor" from the good old days of Clipper. Still useful in the .Net world for anonymous functions with a familiar syntax and of course for VO functions like AScan. The most important difference between codeblocks and regular lambda expressions is that the parameters for a codeblock are usuals (although typed parameters are supported too) and never mandatory.
CURRENCY Decimal A usual type variant for storing numbers with a fixed size precision of 4 digits after the point.
FLOAT Double Same as REAL8.
SYMBOL - A usual type for using a "word" (internally a number) preceded by a # as an index into a String Array.
USUAL - The universal VO data type that can store any kind of value (internally based on the XSharp.__Usual structure)
BINARY - For FoxPro compatibility (encapsulates a byte array)

Tab : Common X# and VO data types and their CLR equivalent


TIP: There are several in-depth explanations about floating point numbers (that are always based on the famous IEEE-754 Standard for Binary Floating-Point Arithmetic in any programming language). One very good article series is http://www.extremeoptimization.com/resources/Articles/FPDotNetConceptsAndFormats.aspx.


Precision and rounding

There are several floating-point numeric types ({{X# Datatypes}) but I recommend using decimal:

  • No rounding errors
  • The best way to handle float values in an Oracle database

One reason for not using Decimal in the early days of .Net was the fact that each value occupies 16 bytes in memory. But even with older PCs that may "only" be equipped with 2 GB memory space should not be an issue anymore. Especially not in the 21st century.


TIP: A 1/3 will return 0.333333 (instead of 0) when the compiler switch vo12 (Clipper Compatible Integer Divisions) is enabled in another dialect than Core.


Type casting

Type casting is the technical term for converting from data type into another. Whereas C# uses the type name in brackets X# uses parentheses. So

? (Int)22/7 3

is a convenient way to cut off the digits after the decimal point.

Another silly example:

? 1/3

results to 0 because each number is treated as an Int. But

? (decimal)1/3

leads to "0,3333333333333333333333333333" which is impressive because it is exactly what I would expect.

Value types and reference types

The most important distinction with .Net data type is not about the data type itself but whether it is a value or a reference type. Whereas a value type variable contains the value, a reference type variable contains the reference to the instance of the type. It's interesting to note, that the value types like Int or Float are based on structures of the type System.ValueType which inherits, as a very class type, from System.Object. contains most of the predefined value types like int, bool or char. Built-in reference types are object and string. Every class definition defines a new reference type.

Under some circumstances, the CLR has to thread a value type as a reference type and therefore has to "box" the value type into a reference type and later "unbox" it. This can be a performance penalty. But since detecting such a situation needs both a good understanding of the inner workings of a .Net language like C# and some knowledge about the underlying IL code, the boxing/unboxing of value is something application developers have to worry about (I don't).

Structure types

Most developers probably do everything with classes. But if the purpose of a class definition is just to encapsulate a data structure, a structure definition might be a better option. A structure type (or struct type) is a value type that can encapsulate data as properties and functions as methods. Just like a class without the overhead that is often not needed. Structure in X# is defined with the Structure keyword as C# uses the struct* keyword.

**Example 6.1: (XS_Structures.prg) **

// Defining a structure

Structure Address
   Internal Property City As String Auto

   Public Method ToString() As String
      Return i"City={Self.City}"
End Structure 

Structure Person
   Internal Property Name As String Auto
   Internal Property Address As Address Auto

   Public Method ToString() As String
      Return i"Name={Self.Name} City={Self.Address.City}"

End Structure

Function Start() As Void
    var a := Address{}{City := "Llanfairpwllgwyngyll"}
    var p := Person{}{Name := "Tom Jones",Address:=a}
    ?p

The little examples prove that in many situations structures behave like classes and are a better choice (I have to admit that I haven't used them very much so far but I think this might change in the future;)Type Formatting

Whoever said "You can't always get what you want?" Anyway, with numbers you will not always get the output you want. .Net offers a rich set of formatting options for numbers and datetime values. For example through the overladed ToString() method.

var d1 := (Single)22/7
var d2 := (Double)22/7
? d1:ToString("F9")
? d2:ToString("F19")

The "F" stands for "Fixed-point" and the number stands for the number of digits in the returned string.

There are plenty of other options and it's possible to left or right pad numbers too. You will find a complete overview in the official documentation for the .Net runtime: https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings.

Constants

A constant is a variable whose value cannot change. Constants are defined inside a class definition with well well-known Const keyword:

Const HyperDriveMode := 42 As Uint16

A constant defined with Define can be used anywhere in the source code:

Define adVarChar := 202 

Whenever more than two constants are used together in the same context, an enum (Chapter 12) is a better alternative.

Handling of null values

One aspect that has been constantly improved with the different C# versions over the years is the handling of null values. Gone are the days when a reference value had to be compared with Null with the == operator or even worse several null checks had to be chained together if the property of an object property that both can be null had to be checked separately.

The following example should illustrate the situation in the "old days of fear of NullReferenceExceptions"*.

First, imagine two simple classes Address and Person:

Class Address
    Internal Property Street As String Auto
    Internal Property City As String Auto

    Public Method ToString() As String
        Return i"City={Self.City} Street={Self.Street}"

End Class

Class Person
    Internal Property Name As String Auto
    Internal Property Address As Address Auto
End Class

Next, there a two functions that return either an Person and Address object or Null:

Function GetAddress() As Address
   var z := Random{}:Next(1,10)
   Return IIf(z > 5, Address{}{City:="Venice Beach",Street:="Ocean Front Walk 1"}, Null)

Function GetPerson() As Person
   var z := Random{}:Next(1,10)
   Return IIf(z > 8, Person{}{Name:="Ernie",Address:=GetAddress()}, Null)

The random numbers will "simulate" the occurrence of null values.

Now a simple print function will print the name and address of a person:

var p := GetPerson() // gets either a person object or null
if p != Null .and. p:Address != Null
   ? i"{p.Name} lives in {p.Address}" 

Since both p and p:Address can be null, a double null comparison was necessary.

Thanks to the ?. operator (or ?: outside a string interpolation) the null check is no longer necessary:

? i"{p?.Name} lives in {p?.Address}" 

The null conditional operator was one of the many improvements that were added to C# 6.0. Since X# follows C# closely, these kinds of conveniences are also part of X#. The following syntax elements are available for better dealing with potential null values.

  1. Add a ? to any value data type name to turn into a Nullable type that has a Value and a HasValue property
  2. The null operators ?. and ?:
  3. Null coalescing operators like ?? and ??=

Nullable types

Nullable types had been introduced with C# 2.0, they are part of X# too.

A nullable type is a value data type that can be assigned a null value (before that this was only possible with reference types). All nullable types are based on the System.Nullable type. They work like a wrapper about any value type (Int32 for example) that provides two additional properties: Value which is the value of a variable and HasValue which is true if the variable has been initialized. Otherwise, it's false which means a variable of that type is null.

To define a nullable type you only have to add a ? to the data type like Int32? or DateTime?.

The following example declares a variable of type DateTime?. It therefore is a new type with a HasValue property that is false if no value had been assigned to that variable.

**Example 6.2: (XS_DateTimeNullValue.prg) **

// Using DateTime? instead of DateTime

Function Start() As Void
    Local d As DateTime?
    d := Null
    ? i"The value of d: {d.HasValue}"
    d := DateTime.Now
    ? i"The value of d: {d.HasValue}"
    Return

Nullable types are a meaningful addition to any language. I am not sure yet if it's a good idea to use nullable types in general instead of the respective value type but it would probably be a good decision.

The null operators ?. and ?:

The operators ?. and ?: make it not necessary to query a property for null before accessing it.

A practical example is accessing the column of a typical GridView control like so:

Self:gridView1:Columns["amount"]:Summary:Add(TotalAmount)

What happens if there is for some reason no column "amount"? A NullReferenceException occurs. Usually, a null query would be necessary:

If Self:gridView1:Columns["amount"] != Null 
    Self:gridView1:Columns["amount"]:Summary:Add(TotalAmount)
EndIf

Using ?: makes such an "old fashioned" null pointer guard superfluous:

Self:gridView1:Columns["amount"]?:Summary:Add(TotalAmount)

The Summary property is only accessed if the column exists.

Of course, ?: cannot be used on the left side of an assignment. The following won't work although at first you might be tempted to try it out:

Self:gridView1:Columns["amount"]?:Width := 240

The following example defines a class Person with an Address property that is an instance of the Address class that has a City property. Thanks to the ?: operator querying the City property is even possible if the Address property is Null. This little improvement is a real convenience and makes the code better readable.

**Example 6.3: (XS_NullOperator.prg) **

// Using the null operators ?. and ?:

Class Address
    Internal Property Street As String Auto
    Internal Property City As String Auto
    Internal Property Zip As String Auto
    Internal Property Country As String Auto

    Public Method ToString() As String
        Return i"{Street},{City},{Zip},{Country}"
    End Method

End Class

Class Person
   Internal Property Name As String Auto
   Internal Property BirthDate As DateTime Auto
   Internal Property Address As Address Auto
End Class

Function Start() As Void
    var p := Person{}{Name := "Alan Alda"}
    // Results a null reference exception
    // var city := p:Adress:City
    // No problemo - city will be null
    var city := p?:Address?:City
    ?i"City = {city} (IsNull={city == Null})"
    p:Address := Address{}{Street := "123 Main St",City :="New York",Zip :="10001",Country :="USA"}
    ? i"Address = {p?.Address}"

The null coalescing operators ?? and ??=

The null coalescing operator ?? returns the value of its left-hand operand if it isn't null otherwise, it evaluates the right-hand operand and returns its result.

The following example also uses the Person and the Address class. The address of a person instance is only assigned to a variable if the property is not null. Otherwise, a new instance of Address will be instantiated and assigned to the variable.

**Example 6.4: (XS_NullCoalescing.prg) **

// the null coalescing operator
// ?? returns the value of its left-hand operand if it isn't null
// otherwise, it evaluates the right-hand operand and returns its result

Class Address
    Internal Property Street As String Auto
    Internal Property City As String Auto
    Internal Property Zip As String Auto
    Internal Property Country As String Auto

    Public Method ToString() As String
        Return i"{Street},{City},{Zip},{Country}"
    End Method

End Class

Class Person
   Internal Property Name As String Auto
   Internal Property BirthDate As DateTime Auto
   Internal Property Address As Address Auto
End Class

Function Start() As Void
    var p := Person{}{Name := "Mike Farrell"}
    var address := p:Address ?? Address{}{City:="No City"}
    ?i"Address = {address}"
    p:Address ??= Address{}{City:="Tokiyo"}
    ?i"Address = {p.address}"

The term "coalescing" arises from the fact that the ?? operator always produces a single value from one of the two values where the first non-null value is the result.

I can only recommend using the null operators whenever it's appropriate. They will lead to more solid programs that throw fewer NullReferenceException exceptions and they make the code better readable too. That does not mean though that using the null operators is always the best choice. There might be situations where the fact that a certain value or property is null will lead to certain actions being taken. In this situation, the traditional way of null handling is still the best (and only) solution.

As already said, null operators cannot used on the left-hand side of an assignment.

Declaring variables

X# offers several keywords for declaring and initializing a variable like Local, Private, or Var. They can be distinguished by scope-defining keywords (like Local and Global) and visible modifiers (like Private and Public).

Keyword Definition Comment
Local A local variable inside a method or function boring;)
Global A global variable that can be accessed everywhere in the application Beware
Private A variable that is local to a class Boring too
Public A variable that can be accessed through the class and from another application if it is used like a library. default
Internal A variable is available outside a class but not outside the application if it's used like a library. practical
Protected A variable can only used in derived classes. rarely needed
Var Used as an alternative to Local to declare a variable without specifying a type that will be determined with the first assignment of a value. Oftern more practical than Local.

Tab 6.1: X# keywords for variable declarations by setting scope and visibility

The assignment of multiple variables in one statement like it's possible in languages like Python is possible in X# too.

Either like so

Local x,y,z As Int

or with initializations:

Local x := 1,y :=2 ,z :=3 As Int

Convenient of course, but it makes the code a little bit harder to read because one of these variable declarations can be easily overlooked. I would recommend this abbreviation only for local variables.

It's not possible to assign an array with several values to several variables (like in Python).

There is no need to declare a local variable with a data type because of the var keyword. A variable declared with var is always statically typed but the type is inferred by the compiler from the data type of the assigned value.

var dlgUser := DialogUser{}

What might be the datatype of dlgUser be? It DialogUser of course.

var is also helpful within a ForEach loop.

**Example 6.5: (XS_ForEachVar.prg) **

// Example for a ForEach loop with a var variable

using System.Collections.Generic

Class City
  Property Name As String Auto
  Property Country As String Auto

  Override Method ToString() As String
   Return i"Name={Self:Name} Country={Self:Country}"

End Class

Function Start() As Void
   var bigCities := List<City>{}{City{}{Name:="New York", Country:="USA"},;
                               City{}{Name:="Vancouver", Country:="USA"},;
                   City{}{Name:="Cairo", Country:="Egypt"}}
    ForEach var City in bigCities
     ? City
    Next

Side question: Can an object be printed in the console? Yes, if the ToString() method is overwritten inside the class definition. The return value of that function decides what will be printed. Otherwise ToString() just returns the full type name so there is always an output.

A little drawback is that a var-variable always has to be initialized with a value.

The case about case sensitivity

Case sensitivity and X# is a tough issue, at least for me. I have to admit that during the first couple of years as an X# developer, I was a little lazy and used different writings for a single variable many times. The punishment always followed immediately when during debugging I was not able to get the value of a variable because I used a different writing than when the variable was declared. So my advice is simple and clear: Be consistent with case sensitivity. It always pays off.

There are several things to consider:

  • Out of the box the X# compiler does not care about case sensitivity. The variables Document and document are the same.
  • Case sensitivity can be turned on by the compiler switch /cs.

For C# developers the whole discussion is completely superfluous.

But it's more than a question of style and habits. Consider the following method declaration:

Method Transfer(document As Document) As Void

Another quiz question for my readers: Is this method declaration allowed with X# or not (hint: this has nothing to do with compiler options)?

The answer is (of course): It depends. With X# 2.13 it's not allowed. But with X# 2.14 the X# compiler compiles this into working code. As already mentioned this has nothing to do with case sensitivity. With X# 2.14 issue #922 had been fixed which caused a problem-resolving method when a type and a local have the same name.

The next question would be: Is it possible, should follow this convention? Again, it depends. If you have a strong C# background or are working hard to gain expertise in this field, it's obvious to use that convention. If you don't care much about C#, my recommendation is not to use a type name as a name for a parameter.

Operators

I won't go through all X# operators since they are all listed in the language reference part of the X# help file but only name a few. And I won't discuss operator precedence and operator overloading (which is possible in X# too and which is amazing) although they are important topics.

It's interesting to note, that X# offers two equal operators: = and ==. I would prefer the second one but there seems to be no difference.

Like in C#, X# has all the "equal combined with operation operators" like +=, */=, or even &= which shortens an expression.

Instead of the classic n := n + 1 n += 1 is shorter and more convenient.

The increment and decrement operators can be either postfix (++i) or prefix (i++).

A little quiz question. How many times does the following loop repeat?

var i := 0
while ++i <= 10
 ? i
end while

10 times as expected. And how many times will the following loop repeat?

i := 0
while i++ <= 10
 ? i
end while

This time it's 11 times because the increment happens after the comparison so that the break condition is true when i equals 10 but i will be incremented one more time.

The classic And an Or operators can be written either as && and || like in C# or like .And. and .Or. like in VO.

Interesting to note is that in the Core dialect the output is either False or True, in the VO dialect it is either .T. or _.F. (the reason for this is that in the core dialect ? calls the official ToString() method whereas with the other dialects ? is mapped to the QOut() function that uses the VO specific AsString() function)

C# lacks the convenient ** operator as an alternative to the ^ operator which is the Xor operator in C#. In X# it's either the tilde ~ or .Xor.

X# does not have the ternary operator ? in combination with :. You have to use the IIf function instead which improves the readability (in my humble opinion).

var wDay := "Monday"
var wDayCount := 0
wDayCount += IIf(wDay == "Monday", 1, 0)
? wDayCount

NOTE: Be careful with the && operator. Except in the Core and Vulcan dialects, these characters can also be used as an alternative for the // single-line comments. Therefore, switching a project from Vulcan to VO (for example) could mean that parts of logic expressions are ignored because the part after && is ignored (as Robert said in a comment, "You don't want to be bitten by this" expected behavior).


Operator overloading

Some operators are "overloaded". This means, that they can be used with several data types and each overloaded operator might do something completely different. The arithmetic operators for example can be used not only with numbers but with string or DateTime values too. The + operator will concatenate two strings, and the - operator will return the time span between two dates. But the - is not overloaded for strings and the + is not for DateTime values.

Try something like this

Function Start() As Void
  ? (DateTime.Parse("22:30") - DateTime.Parse("14:44")):TotalHours

The result will be the difference in hours between the two-time values.

How operator overloading can be done with existing operators in X# is explained in Chapter 11.

Scope

Variables, class definitions, and members of a class all have a scope, also called visibility. The scope determines in what of the application a variable or a type definition is "visible" so that their values and metadata are accessible.

There are several scope levels" in X#

  1. Namespace
  2. File
  3. Global
  4. Class
  5. Method
  6. Block

C# developers beware: X# has global variables for historical reasons. If "globals" are used wisely, they should not be considered either evil or harmful (or both). Although encapsulation is an important goal to achieve, if a connection string for example is used in several classes it can be a global constant as well and does not have to be an argument for each method that should use it.

Global variables are defined with the global keyword - but only outside a class definition (either inside or outside a namespace declaration)

The following descriptions of the scope are for variables only, but they of course also apply to any other elements that have a scope.

Variables are only visible outside the class definition if they are not declared with Private.

A local variable is always invisible outside its block. The block is usually a method definition, but it can be also a control structure like a loop, an if/else or a try/catch block.

As probably all static-typed programming languages do, X# uses a technique called lexical scope. This means, that the name resolution (and therefore the scope) of a variable depends on its location in the source code (inside a method definition for example) and its lexical context (also called static context), which is defined by where the variable is defined.

The scope is top-down. That means for example that inside a block all the variables from a higher scope are visible but not vice versa. At the class level, variables defined inside a method or block are not visible but a variable defined at the class level can be read and written to inside every method of that class.

Date and time values

The .Net runtime offers a general DateTime type that includes a date and optional time information. This is a very flexible and convenient type that offers many methods for dealing with nearly all aspects of date and time issues. Please note, that the .Net runtime does not have either a Date or a Time type. If the time part of a DateTime is not used, its value is "00:00:00".

When porting VO code to X#, I recommend replacing functions like CToD() in combination with methods like SubStr() to access certain parts of date with values based on the DateTime type because it makes the code simpler and better readable.

Please note, that there is a separate Calendar class (in the System.Globalization namespace) because dealing with dates always depends on a specific calendar. To be globally usable, the .Net runtime supports more than a dozen different calendars.

Converting a string to a DateTime

Converting strings to a DateTime is usually done with the Parse() method. If the string value does not represent a valid string, a System.FormatException occurs. As the following example shows, the Parse() method is flexible so it's quite a challenge to come up with a supposed valid string that turns out to be not valid.

**Example 6.6: (XS_DateTimeConvert1.prg) **

// Converting strings to a DateTime

Function Start() As Void
   Local dateStr := "4.7.23" As String
   var d := DateTime.Parse(dateStr)
   // Use ToString() with default formating
   ? d
   // Now with a specific time
   dateStr := "4. 7 2023 8:23"
   d := DateTime.Parse(dateStr)
   ? d
   // Parse is flexible
   dateStr := "4. 7 2023"
   d := DateTime.Parse(dateStr)
   ? d
   // Parse is really flexible
   dateStr := "4 july"
   d := DateTime.Parse(dateStr)
   ? d

Converting a DateTime to a string

Converting a DateTime to a string can be done either with methods like ToShortDateString() or ToLongTimeString() or with the general ToString() method that is overloaded and accepts a format string where each character has a certain meaning.

The following example contains several simple formatting methods.

**Example 6.7: (XS_DateTimeConvert2.prg) **

// Converting a DateTime to a string

Function Start() As Void
   Local dateStr := "4.7.23" As String
   ? DateTime.Parse(dateStr):ToShortDateString()
   ? DateTime.Parse(dateStr):ToLongDateString()
   ? DateTime.Parse(dateStr):ToString("dd-MM-yyyy")
   // Now in French
   var ciFr := System.Globalization.CultureInfo{"fr-FR"}
   ? DateTime.Parse(dateStr):ToString("D", ciFr)

Formating a DateTime so that it fulfills any wish

As stated several times in this short section, formatting a DateTime is flexible and easy thanks to the ToString() method and a formatting string that may contain several characters where each represents a specific part of the date and the time part.

The table contains only a few of the specifiers available. You will find a complete list in the documentation:

https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings

It's even possible to define your custom format and write a format provider so that any datetime value based on this format can be processed by Parse() and ToString() too (or let ChatGPT write that code for you;)

Format specifier Meaning
d Short date format
D Long date format
t Short time format
T Long time format
dd Day as a number
ddd Day name abbreviated
dddd Day name full
M Day and month
MM Month as a number
MMMM Month name full
Y Month and year
yy Year without century
yyy Year with century
fff Milliseconds

Tab 6.2: A few of the more important format specifiers

The following example uses a few of the format specifiers to get more specific parts of a datetime like the milliseconds or the time zone.

**Example 6.8: (XS_DateTimeFormat.prg) **

// Getting different parts of a DateTime with formating strings

Function Start() As Void
    var todayDate := DateTime:Now
    ? i"Today: {todayDate.ToString()}"
    ? i"Year: {todayDate.ToString(""yyyy"")}"
    ? i"Month: {todayDate.ToString(""MM"")}"
    ? i"Day: {todayDate.ToString(""dd"")}"
    ? i"Hour: {todayDate.ToString(""HH"")}"
    ? i"Minutes: {todayDate.ToString(""mm"")}"
    ? i"Seconds: {todayDate.ToString(""ss"")}"
    ? i"Milliseconds: {todayDate.ToString(""fff"")}"

Testing for a valid DateTime

Testing for a valid datetime can be done with the TryParse() method. If the string argument is a valid datetime it returns true and stores the converted datetime object into a separate variable.

**Example 6.9: (XS_DateTimeTest.prg) **

// Testing for a valid datetime with TryParse

Function Start() As Void
   var testDates := <String>{"1.4.2000", "29.2.2001","3. Jan","01-11-2023", "4 May 2012"}
   Local dateValue As DateTime
   ForEach var testDate in testDates
     // there is no DateTime?.TryParse
     If DateTime.TryParse(testDate, out dateValue) 
        ? dateValue
     Else
       ? i"{testDate} is not a valid DateTime (sorry)"
     EndIf
   Next

Strings

As in any programming language, a string is a sequence of characters. Because X# is based on the .Net runtime, it uses its "string infrastructure" as well. That means that an X# string is a .Net string which is a sequence of Unicode characters. That means that each character is a char object that contains a 16-bit value. Of course, there is no (real) limitation for the length of a string which is provided by its length property.

Speaking of lengthy strings, thanks to the (heavy) overloaded constructor, a String can be initialized like this:

Console.WriteLine(String{'=', 80})

This will print a string of 80 equal signs. The apostrophes are necessary so that the compiler treats the first argument as a char. Otherwise, the string would have to be preceded by a 'c':

Console.WriteLine(String{c"=", 80})

One of the most important aspects of Strings is that they are immutable (which is good). Whenever a string variable is assigned a new value, a new string will be created in memory and become the new string. That can make concatenating strings by += slow inside large loops. If this situation will ever occur in your programming, use a StringBuilder instead.


TIP: There is a lengthy discussion about the question of why strings are immutable in .Net:

https://stackoverflow.com/questions/2365272/why-net-string-is-immutable

You will need some time to read through all the topics.


If the encoding matters, the Encoding class (namespace System.Text.Encoding) offers conversion methods like ToByte() and GetString(). Whereas ToByte() converts an encoded string into a byte array, GetStrings() converts a byte array into an encoded string again.

Strings can be indexed in C++ or Python. For example.

Local s1 := "Summertime" As String
?s1[0]
ForEach var c in s1
  ? c
Next

The result of indexing is not another string but a char object.

Using a string method therefore is not possible:

s1[0]:ToLower()

To make it work, a ToString() in the middle is necessary:

s1[0]:ToString():ToLower()

Another option is to use the static ToLower() method of the char class. But since this method needs a CultureInfo object as a second argument, the whole conversion might be a little bit too cumbersome:

var ci := System.Globalization.CultureInfo.CurrentCulture
?Char.ToLower(s1[0], ci)

String comparisons

String comparison is not as easy as it may sound although it is not complicated either. The .Net way of string comparison is to use the Equals method of the String class instead of the == operator although this operator will deliver the correct result in approx. 99 % of all cases. There may be some subtle differences but they probably only occur in very special situations. One advantage of the Equals method is the fact that case-insensitivity can be turned on by an optional parameter:

var n1 := "Summer"
var n2 := "SUMMER"
var n3 := "HotSummerTour"
? n1 = n2                 // False
? n1 == n2                // False
? String.Equals(n1,n2)    // False
? String.Equals(n1,n2, StringComparison.CurrentCultureIgnoreCase) // True


Tip: There is a lengthy discussion on StackOverflow about the question "== or Equals for string comparison?": https://stackoverflow.com/questions/1659097/why-would-you-use-string-equals-over.


There is no equivalent in C# to the like operator in Visual Basic. The perfect alternative without having to use VO functions (which are always an option) is to use a regular expression (or Regex for short). This is done by the Regex class in the namespace System.Text.RegularExpressions and its Match() method.

? Regex.IsMatch(n3, "summer", RegexOptions.IgnoreCase)  // True

The simplest possible "regular expression" is just a name that will be "matched" in another string. Life can be so simple;)

String comparison with >, >=,> and >=

Interestingly, the standard operators >,<,<= and >= are not implemented in C# for strings. The rationale is, that string comparison is a surprisingly complex topic, and therefore static methods like the Equals() or Compare() method of the String class should be used because they allow us to take culture and other system dependent differences into account.


NOTE: The topic is explained in the official documentation in great detail and with many examples: https://learn.microsoft.com/en-us/dotnet/csharp/how-to/compare-strings


Since operators like > or > are not only very convenient but have been used in other languages for decades, X# supports this operator too. A a < b is automatically translated by the X# compiler to a String.Compare(a,b) < 0. If you enable the compiler option /vo13 (Compatible String Comparisons), the comparison will be done with the X# runtime instead of the .Net runtime. The code will compile to XSharp.RT.Functions.__StringCompare(a, b) < 0. Why is this distinction important? Because the X# runtime function will respect both the SetCollation() and the SetExact() setting. This means, that if b is shorter than a and SetExact() == False, only the first b:Length characters of a will be compared with b. When the length of b is zero, the strings are assumed to be equal (if SetExact() = false).

String interpolation

A harmless i before a string allows a powerful string interpolation. That means, that any expression (as long as it does not get too complicated) inside a pair of {} will be evaluated and the {} will be replaced with the result. The i is equivalent to the $ in C# (although with X# 2.x the X# equivalent is not as flexible yet).

var a := 4, b:=5
? i"The sum of {a} and {b} = {a+b}"
? "Today is " + DateTime.Now:ToString()
? i"Today is {DateTime.Now}"

You may have noticed that inside the curly braces, the dot operator is used instead of the colon operator. If you opt for the colon, nothing will happen.

In general string interpolation is very convenient because it converts numbers and DateTime values automatically to strings and therefore makes explicit ToString() calls unnecessary. I would prefer this syntax over the Ntrim() function.

An expression that involves accessing an object property will also evaluate:

? i"Id={ta.Rows[0][""id""]}"

Again, don't forget to use the dot instead of the colon. Otherwise, the expression won't be evaluated.

String interpolation also works with Strings and their methods.

var s := "summertime"

? i"{s[0].ToString().ToUpper()}"

? i"{""summertime"".ToUpper()}"

Here is a really "complicated" expression inside a string that will get evaluated thanks to string interpolation:

infoMessage := i"~~~ Fensternachricht {C2Hex(LoWord((DWord)m:Msg):ToString())} ~~~"

String interpolation has improved a lot with the latest X# versions. But it still can get a little tricky, when the expression inside the {} uses quotes.

The following string interpolation is processed correctly:

? i"{""Regexes are lame"".replace(""lame"",""cool"")}"

A simple "workaround" for cases where string interpolation does not work or seems not to work is to use the classic .Net way of including values inside a String by using the static Format method of the String class and its {} place holders:

? String.Format("Id={0}", ta:Rows[0]["id"])

Another (very) nice feature of X# is the fact that a " (quote) inside a string needs not to be escaped. It just has to be accompanied by another quote. This is very convenient for aliases inside a SQL statement.

sqlText := "Select Last_Name ""Name"", First_Name ""First"" From Adresses"

NOTE: Please beware that using the i technique too intensively can pose a little danger. Consider the following concatenated SQL statement:

sqlText := i"Select * From Table Where Field1 == '{value1}' and"
sqlText += "Field2 == '{value2}' Ordered by Field1"

Who can spot the error without having to compile the line first? Because there is no i in the second line, the placeholder {value2} won't be replaced by the value of the variable value2. Now imagine an SQL statement that consists of several lines. Omitting an i in one of these lines will cause an error that can be hard to find.


My recommendation is to use i for things like error messages or other text lines with variable values, but not for SQL statements. Or do a search within Visual Studio that searches for quoted lines that contain an {} but do not start with an i.

Formatting

String interpolation is not that flexible like the $ notation in C# when it comes to formatting, To format a number or DateTime value, the ToString() method is still necessary:

The following interpolation adds as many zeros as needed to output 10 digits:

? i"i={i.ToString(""0000000000"")}"

Output formatting with ToString() is also an option for outputting rounded values.

var r := 22d / 7
? i">>The result is {r.ToString(""f2"")}"

>>The result is 3,14

Including "things" like \r, \n or \t

If a string contains for whatever reason special characters like a tab (\t) or a line break (\n) these characters would normally be treated as literals. If they should play out their "special magic" like causing a line break while printing the string in the console window, for example, the whole string has to be preceded by an e. Great stuff.

var text := e"\tKey\tValue"

What if the string also should contain expressions for string interpolation? No problem, just combine the e with an i.

An IsAlpha()-Method as an extension method

The .Net runtime does not contain an IsAlpha() method for strings that would test if a string contains only either digits or characters (but no special characters). There is one in the VO library, of course, so nothing is missing.

Otherwise, like in C#, you would have to define such a method as an extension method for the string type.

Extension methods are explained in more detail in chapter 11. Here is only a short overview (that already explains everything important). An extension method extends a certain type. This is usually a class from the .Net runtime like String but it could be any class of course. The idea is that using an extension method feels more "natural" than having to use a static method from a "helper class" or a function in X#. Since C# has no equivalent for functions, extension methods were a real improvement when they were introduced with C# 3.0. But the main reason for their invention was that they allowed to add "LINQ methods" like First() or Where() to any object based on a class that implements IEnumarable for example.

Extension methods are regular static methods with two exceptions:

  1. The parameter list always starts with the "Self" modifier (instead of this which is used in C#).
  2. The data type of the first parameter denotes the class that the method extends.

The following extension method IsAlpha() therefore extends the String class:

Static Method IsAlpha(Self str As String) As Boolean

The compiler now has no problems with something like "Fahrenheit452".IsAlpha()" (in contrast to people who know classic Science Fiction movies).

More on extension methods in chapter 11.

**Example 6.10: (XS_StringIsAlpha.prg) **

// Example for an IsAlpha extension method

using System.LINQ

Class StringEx

   Static Method IsAlpha(Self str As String) As Boolean
      Return str:ToCharArray():Where({ c => Char.IsLetter(c)}):ToList():Count == str:Length

End Class

Function Start() As Void
   var s1 := "Andromeda"
   ? s1:IsAlpha()
   var s2 := "Fahrenheit 452"
   ? s2:IsAlpha()

Getting the nth character of a string

This is usually done with either the SubStr() function from the VO dialect or the SubString() method of the String class. Both return a String but the first character has the index 1 with SubStr() and 0 with SubString().

**Example 6.11: (XS_SubstringComparison.prg) **

// Comparison of SubStr() function and SubString() method
// Compile with /dialect:VO /r:RuntimeLibs/XSharp.rt.dll /r:RuntimeLibs/XSharp.Core.dll

Function Start() As Void
   var s1 := "Rio Bravo"
   ?SubStr(s1,1,3)        // Returns Rio
   ?s1:Substring(0,1)     // Returns R
   ?s1:Substring(0,3)     // Returns Rio
   ?s1:Substring(4)       // Returns Bravo
   ?s1:Substring(4,1)     // Returns B

The following expression returns the 5th character (as a string) which is the 'B':

var s1 := "Rio Bravo"
?s1:Substring(4,1)

Another option is the ToCharArray() method which returns all the characters of a string as an array of Char objects. But since the use of indices is not very flexible with Cahors 2.x (C# already supports ranges to access only a part of an array), LINQ comes to the rescue again.

The following example returns the first and the third characters from a string as char objects.

var s1 := "Odysee 2001 in spaces"
var charList := s1:ToCharArray():Where({c, i => i == 0 || i == 2}):ToList()

It's still exciting for me that this kind of syntax is possible not only in C# but in X# too.

Example 6.12: (XS_String2Chars.prg) ** You will find the source code in the repository

What does "strings are immutable" really mean?

You have probably heard the phrase "strings are immutable" at least one time, but probably many times before. It simply means that is not possible to assign a new value to a string. But, of course, it's possible. Otherwise, the world of software development would stand still immediately. Whenever a string gets a new value, a new string is created in memory and the new value is copied into it. Since this could become a performance issue when a string is concatenated in a loop many times, the .Net runtime offers the StringBuilder class which should always be used in such cases.

The immutability of strings might be one reason why the String class does not offer a ReplaceAt() method for replacing a single char within a string. There are several ways to add such functionality if ever needed. The following example uses an extension method which is my preferred way. But this is only a personal choice. You should never, never feel bad if you prefer a function.

The ReplaceAt() method uses the ToCharArray() method to get an array of all characters, replaces a single character with another one, and recreates a new String by using one of the several constructors that accept a Char array as an argument.

**Example 6.13: (XS_StringReplaceAt.prg) **

// replacing a char in a string - always good as an extension method

using System

Class StringEx

    Static Method ReplaceAt(Self str As String, pos As Int, newChar As Char) As String
        If String.IsNullOrEmpty(str)
           throw ArgumentNullException{"str"}
        EndIf
        var chars := str:ToCharArray()
        chars[pos] := newChar
        Return String{chars}

End Class

Function Start() As Void
   var s1 := "Tomorow never dyes"
   ? s1
   s1 := s1:ReplaceAt(16, c"i")
   ? s1

The concept of immutability does not belong exclusively to X# or C#. Other languages, that are based on a VM (Virtual Machine) like Java have it too.


NOTE: As usual you will find a lot of examples that explain the concepts of string immutability in great length. On SO for example: https://stackoverflow.com/questions/8798403/string-is-immutable-what-exactly-is-the-meaning.


Late binding with X

Yes, late binding. Whoever was a developer in the late 80s or 90s probably just took it for granted that a certain property could be used with an object no matter what type the object was (if it was typed at all). These late-bound calls are still a topic in the year 2023.

The typical use case for late binding is scenarios that involve COM objects that only provide a IDispatch interface. This means, that the object does not provide its interface with members. It only provides a general interface with a general method to query the IDs of all members available through that object.

Late binding was the only way to make Microsoft Office automation work in the early years before Microsoft provided the COM Interop assemblies that allow early binding and make accessing the "Office API" really easy.


Important: COM Objects were also called OLE Objects many years ago. There are probably only a few people on this planet who can explain the difference (Kraig Broksmith is one of them;)


The dynamic type

Although object is the most general type, assigning the instance of an object to a object variable does not mean that the members of that object can be accessed through that object. Same with usual. For late binding situations like this, C# 4.0 introduced the dynamic type which is available in X# too. It was introduced primarily to make C# programs able to access APIs in dynamic languages like Python or Ruby and for simpler access to COM objects. But it can be used for simpler requirements as well.

The first example only shows how to achieve late binding with the dynamic keyword just for the sake of the demonstration. The example itself has no practical use. Since the variable actionController is declared with dynamic instead of object or usual, it can be assigned any kind of object.

**Example 6.14: (XS_LateBinding.prg) **

// Example for late binding

Class XAction
   Internal Method Action1() As Void
      ? "This is Action 1"

   Internal Method Action2(Count As Int) As Void
       For Local i := 1 UpTo Count
           ? "This is Action 2"
       Next

End Class

Function Start() As Void
    // Won't compile
    // Local actionController As Object
    Local actionController As Usual
    // Local actionController As Dynamic
    actionController := XAction{}
    actionController:Action1()
    actionController:Action2(3)


NOTE: The dynamic type of C# and the underlying dynamic runtime is a huge topic covered extensively in the Microsoft documentation. An interesting discussion on StackOverflow gives (as usual) lots of details about the dynamic keyword of C#: https://stackoverflow.com/questions/2690623/what-is-the-dynamic-type-in-c-sharp-4-0-used-for.



NOTE: There is a subtle difference between Dynamic and late bound when it comes to case sensitivity of member names. With the dynamic type the lookup of members is case-sensitive, "late bound" code is case-insensitive.


Using COM interfaces through late binding

In the year 2023, there is not much need for COM interfaces anymore. In the 90s, COM was used for several things that could not be accomplished with a programming language and its library functions like processing Xml documents, automating Office applications like Excel and Word, and a few other things like using the FileSystemHost for easy access to the file system. Nowadays, there are only a few occasions left where using the COM interface of an application might be necessary. One of them is accessing the extended file properties that are provided through Windows Explorer. This will discussed in a later chapter.

The following example is primarily another example for late binding and the dynamic keyword. It uses the COM object WScript.Shell through its "ProgId" and shows a simple popup messagebox that will disappear after a few seconds (this is something that cannot be accomplished with the .Net runtime so easily;).

**Example 6.15: (XS_WScriptShell.prg) **

// using the COM object WScript.Shell

Function Start() As Void
  Local progId := "WScript.Shell" As String
  Local shellType := Type.GetTypeFromProgId(progId) As Type
  Local shell := Activator.CreateInstance(shellType) As Dynamic
  shell:Popup("Yes, we can!",3,"Info",48)

Boxing and unboxing

Boxing and unboxing are two important terms every X# developer at least should know about although this knowledge might have little practical value.

Boxing and unboxing are attributes of the .Net type system and the way it works.

Let's summarize (one more time) the basics of the .Net type system. They are value types and reference types. Value types are containers for data, reference types contain a pointer to another value or reference type. Since the designer of the type system wanted to have a single hierarchy with System.Object at the top they had to bring these two opposite concepts "under one hat". This is done by boxing and unboxing.

  • Boxing places a value type in an untyped reference object. So the value type can be used when a reference type is expected.
  • Unboxing extracts a copy of a boxed value type from the box.

Boxing and unboxing are necessary so that a System.Object type can be used when a reference type is expected. This is very convenient or simply necessary because otherwise programming with a .Net language would be probably a little bit more complicated.

A simple ToString() method call with an object type will lead to boxing and unboxing.

Boxing and unboxing are automatically done by the compiler which includes the required IL instructions.

Alt The compiler inserts a box command for the boxing operation Fig 6.1: The compiler inserts an IL-box instruction for each boxing operation

Boxing converts a value type to a reference type by creating a new object on the local heap and copying the value into this object. Therefore the box contains a copy of the value of the value type object. When that value is accessed at a later time, a copy of that value is created and returned.

Although boxing and unboxing are simple operations, they cost extra cycles that could slow down the performance (I have no idea if this is measurable but that's what the experts are telling developers).

So, unnecessary boxing and unboxing should be avoided in certain situations like large loops or other intense data operations that process a lot of values where boxing and unboxing might occur.

And how can you do that?

By following a few simple rules like:

  1. Use generic classes and generic methods because there is boxing/unboxing involved.
  2. Avoid Object as a parameter or return type of method.
  3. Pass a string to methods like WriteLine that expects System.Object args when called with a format argument.

There may be more rules like that.

I have to admit that I never paid much attention to these rules but I found it always very natural to avoid Object arguments and to use generics.

There can be even situations where the boxing effect can lead to subtle bugs (or better "unexpected behavior").

I found the following example in the very good book "Effective C#" by Bill Wagner (Addison Wesley).

The following example shows a "bug" that results from the fact that a struct is used for the members of a generic collection instead of a class. When an element of the collection gets a new value through a variable, a ToString() still prints the old value when the object is accessed as part of the collection.

**Example 6.16: (XS_BoxingBug.prg) **

// Shows a subtle "bug" where boxing is the cause
using System.Collections.Generic

Struct Person
  Property Name As String Auto
  Override Method ToString() As String
   Return Name
End Struct

Function Start() As Void
  var personList := List<Person>{}
  var p := Person{}{Name := "Old Guy"}
  personList:Add(p)

  // Try to change the name
  var p2 := personList[0]
  // Would work of course if Person was a reference type
  p2:Name := "New Guy"

  // Bug: Writes the old name because unboxing results to a value type copy
  ? personList[0]:ToString()

Of course, it's not a real bug. It's more like a, what was the name again?, right, a feature:)