Chapter 9

Arrays, Lists and Dictionaries

This is an important chapter for (at least) two reasons. First, arrays and especially (generic) lists are important data structures. And second, there is hardly another topic where X# that has its roots in the 80s differs more from a "modern" language like C#. The good news is, as always, there is no need to choose between "old" and "new". VO style arrays, codeblocks, and LINQ with lambdas and generic lists can be combined in a single application if the developer wishes to do so.

Arrays overview

Arrays are a well known entity in any programming language since the invention of Fortran in the mid-50s of the last century (I am looking forward to the 100th birthday party for Fortran, Cobol, and LISP although I am not sure that I might be able to attend;) so I won't go into any details.

Arrays have been an important part of VO from the beginning, the VO library contains many powerful functions like AAdd, ADel, AScan, and so on.

This section is about working with Arrays the ".Net way" because this is what C# developers who want to learn X# are mostly interested in. Using Arrays the .Net way does not bring any significant advantages compared to the "VO way" except for more consistency which is always good. I recommend using Lists anyway because they are more flexible but if someone is used to Arrays, especially when they are multi-dimensioned, that's no problem of course (at least when the structure of the multidimensional array is documented somewhere;).

There is no data type Array in .Net. Well, there is an Array class but it's an abstract base class with a set of approximately two dozen methods. So it's possible to declare a variable of Array and use the static methods, but it's not possible to instantiate an Array.

To quote the Microsoft documentation:

The Array class provides methods for creating, manipulating, searching, and sorting arrays, thereby serving as the base class for all arrays in the common language runtime.

Just in case: System.Array documentation

Although it's not possible to create new instances of the Array class, its methods like BinarySearch, Copy, Find, Resize, Reverse, or Sort can be used with an existing array as an argument.

Declaring an array the VO way

In the good, old days of Windows application development using arrays was made as easy and comfortable as possible by languages like VO because all data outside a database were kept in arrays. There were no "modern" alternatives like lists or dictionaries available.

The VO syntax for defining an initialized array is still possible when compiling with the VO dialect:

var zahlen[10]
zahlen[1] := 1000
? zahlen[1]

In the year 2023 (still hard to believe sometimes) this syntax is of historical interest only. A "modern" version for the VO dialect is to initialize an array like this:

Local zahlen := {11,22,33,44,55,66}
? zahlen[1]

or with the formal Array declaration and without initialization values and adding them later:

   Local zahlen := Array{}
   AAdd(zahlen, 11)
   AAdd(zahlen, 22)
   AAdd(zahlen, 33)
   ? zahlen[1]

Since no data types have been used so far, all values are usuals including the array itself.

Declaring an array the .Net way

Whereas VO arrays are declared with Array as the data type, .Net arrays are declared by appending a pair of brackets to the data type.

The following command declares a Int32 array:

var numbers As Int32[]

The array is not yet initialized so it's not possible to store anything into that array. More on this later. The following example creates and initializes a .Net array with three elements and prints out each element:

Local numbers := <Int32>{11,22,33} As Int32[]
? numbers[1]
? numbers[2]
? numbers[3]

11
22
33

Special topics about using arrays

This section contains some useful information for using arrays. Always with consideration that (generic) Lists and Dictionaries offer much more flexibility.

0 or 1 based?

To make a long story short: Both VO and .Net Arrays are 1-based in X# until the compiler switch /az is used which switches to 0-based arrays.

A short example will hopefully shed some light on the consequences.

**Example 9.1: (XS_ArrayLength.prg) **

// Getting the length of an array
// compile with xsc .\XS_ArrayLength.prg /dialect:vo /r:XSharp.rt.dll /r:XSharp.Core.dll                                             

Function Start() as void
   Local Numbers1 := {1,3,5,7,11} As Array
   For Local i := 1 UpTo ALen(Numbers1)
     ? i"Index {i} => {Numbers1[i]}"
   Next
   Local Numbers2 := <Int>{1,3,5,7,11} As Int32[]
   For Local i := 1 UpTo Numbers2:Length
     ? i"Index {i} => {Numbers2[i]}"
   Next

When compiled without the /az switch X# assumes a 1-based array type both for the VO and the .Net array and all numbers are printed out.

When compiling with the /az switch two things have changed:

  1. The first number is skipped because the index is 0
  2. An ArgumentException is thrown because there is no element with the index = Length of Array

Which to choose?

I think it depends if a developer starts a new project or is porting a VO project. Since "the whole world" is used to 0-based arrays I recommend using them in X# too which means setting the /az switch in the project settings. The best solution is to avoid arrays if possible. Not because they are old or "bad" but because generic Lists and the ArrayList class are more flexible.

Another argument is "pro List": Lists and Collections are always 0-based. This is one reason that I recommend using the ForEach command because it "knows" that an array is 1-based and the others are 0-based.

Declaring an empty array

An array variable uses an empty pair of [] with the data type like

Local names As String[]
Local values As Decimal[]
Local flags as Logic[]

Remember: [] always follows the type not the name of the variable.


These arrays are uninitialized so there is no way of putting a value in. They can only be used by assigning an array to them.

Getting the length of an array

Since ALen() won't work with a .Net array, it has to be either the Length property or the GetUpperBound() method. Later method expects a dimension value that starts with 0.

Local Numbers := Int[]{2} As Int[]
Numbers[1] := 100
Numbers[2] := 200
? Numbers:Length
2

Test if an array is empty

There is no IsEmpty() method for an array and we would have to define first what empty means. An array with 0 values, empty strings null values, or all of them? For VO arrays, the AScan() function is usually a good solution. The function expects an array as the first and a code block (which is another term for a lambda expression) as the second parameter. Please look in the X# help file for all the details.

The following example tests if an array with integers contains a value that is not 0.

Local a1 := {0,0,0,0,0} As Array
AScan(a1, {|z| z > 0}) > 0

Example 9.2: (XS_EmptyArrayTest1.prg) ** You will find the source code in the repository

For a .Net array, there is always LINQ as a flexible alternative.

The following example tests if a .Net array with integers contains a value that is not 0.

Local a1 := <Int32>{0,0,0,0,0} As Int32[]
a:Where({z => z <> 0}):ToList():Count == 0

Example 9.3: (XS_EmptyArrayTest2.prg) ** You will find the source code in the repository


NOTE: If while trying out the different options for dealing with VO and .Net arrays an error XS9008: Untyped arrays are not available in the selected dialect 'Core' occurs it probably has something to do with a wrong way of declaring or initializing the array.


Dynamic arrays

VO had an excellent "support" for arrays from the beginning. Much better than Visual Basic for example. The syntax for defining a dynamic array is still available in the VO dialect.

var aItem := { { 1,"OOP", "Soup"},;
               { 2, "AOT", "Cherrypie"};
             }

AAdd(aItem, { 3, "SOLID", "Enchiladas"})

?i"I like {aItem[3][3]}"

Example 9.4: (XS_DynamicArraysVO.prg) ** You will find the source code in the repository

In the core dialect initializing an array with values is not that simple. For a good reason, because there are much better options for a nested data structure.

The following example declares and initializes a two-dimensional array using the core dialect:

var aItem := <Int32[]>{ {1,2,3},;
                        { 11,22,33};
                      }

One more reason not to do it is way is the fact, that it's not possible to use Object as the data type so all the values have to be of the same type.

Example 9.5: (XS_DynamicArraysCore.prg) ** You will find the source code in the repository

It can be done with the generic list class List but this is also not a programming style I would recommend because it makes code hard to read and maintain. And why use Object if there is a better way like using a typed Struct?

**Example 9.6: (XS_DynamicArrayWithList.prg) **

// A small example for using a "dynamic Array" the .Net way - but I would consider "bad practice"

using System.Collections.Generic

Function Start() As Void
  var aItem := List<object>{}{List<Object>{}{1,"Spagetti",3},;
                              List<Object>{}{11,"Rigatoni",33};
                             }
  aItem:Add(List<Object>{}{111,"Pizza",333})

  // not possible because the list element type is object
  // error: Cannot apply indexing with [] to an expression of type `object'
  // ?aItem[0][1]
  // Works, but too complicated
  ?i"I really like {((List<Object>)aItem[2])[1]}"

Initializing a dynamic array

The following command won't compile.

Local values := {1,2,3} As Decimal[]

The error message says it all: "error XS0029: Cannot implicitly convert type 'array' to 'decimal[]'". So something is wrong with the initialization value which is a VO Array.

It's necessary to add the type explicitly.

Local values := <Decimal>{1,2,3} As Decimal[]

note: As stated before, the index in X# for the first elements starts with 1 not with 0 like in C#.

The following loop will result in an IndexOutOfRangeException:

For Local i := 0 UpTo Values:Length - 1
    ? Values[i]
Next

It has to be this way.

For Local i := 1 UpTo Values:Length
    ?Values[i]
Next

As already mentioned, I prefer the ForEach command, although it might be a little slower, because it always "knows" how to enumerate an array or a list.

ForEach var v in Values
    ?v
Next

Initializing a fixed sized array

The syntax for initializing a fixed-size array is a little different.

Local values := Decimal[]{3} As Decimal[]

This has already been addressed but just in case: Is it possible to use VO functions like AAdd or ASort with a .Net array? Answer: No.

Getting the last elements of an array (or list)

Getting the last elements of an array or list could be a little easier than it is with X#. On the other hand, the ^ operator that was introduced with C# 9.0 lacks consistency (it used a 1-based index numbering). The X# team discussed implementing the operator but decided not to do so. Maybe, somebody comes up with a really good syntax for addressing list elements from behind.

In the meantime, there is some simple arithmetic needed for getting the elements of an array or list from the end.

The following functions return the last n elements of an array as another array.

```visual basic Function GetLastNumbers(a as Int[], n As Int) As Int[] var tmpList := List{} For Local i:= n - 1 DownTo 0 tmpList:Add(a[a:Length - i]) Next Return tmpList:ToArray()


**Example 9.7: (XS_ArrayLastElements1.prg) **
*You will find the source code in the repository*

It gets really easy with LINQ and the combination of a *reverse()* and a *take()* method of the *Enumerable* class and a `using System.Linq`:

```visual basic
Function GetLastNumbersEx(a As Int[], n As Int) As Int[]
   Return Enumerable.Reverse(a):Take(n):Reverse():ToArray()

LINQ will explained in Chapter 12 in more detail.

Example 9.8: (XS_ArrayLastElements2.prg) ** You will find the source code in the repository

Alt Getting the last elements of an array or list Fig 9.1: Getting the last elements of an array or list

Multidimensional arrays

The syntax for declaring a multidimensional array the .Net way is a little bit "special" but OK.

**Example 9.9: (XS_ArrayMultidimensional.prg) **

// Creating a two dimensional .Net array

Function Start() as void
  Local Numbers := Int[,]{2,2} As Int[,]
  Numbers [1,1] := 100
  Numbers [1,2] := 111
  Numbers [2,1] := 200
  Numbers [2,2] := 222
  For Local i := 1 UpTo Numbers:GetUpperBound(0) + 1
    For Local j := 1 UpTo Numbers:GetUpperBound(1) + 1
      ? Numbers[i,j]
    Next
  Next

GetUpperBound() always gets the zero-based length of the array in the given dimension (I somewhere read that a .Net array can have up to 63 dimensions which should be enough for any string theory science).)

There is also a GetLowerBound() method but I am not sure what it's good for since the lower dimension is supposed to be always 0 and the method does not work with a VO array.

Well, in the .Net world, you can always suspect a surprise. In this case, it is a rarely (if at all) used method to create an Array through the factory method CreateInstance (thanks to a StackOverflow user who reminded me of this forgotten "jewel").

The following examples prove that in really rare circumstances the lower bound of a .Net array can be non-zero.

The example defines a 3x3 int 2D array but the upper bound of the second dimension starts with 1 and not 0 (I hope I got that right).

**Example 9.10: (XS_ArrayUpperbound.prg) **

// Getting the dimensions of a multi dimensional array

// The upper bound of an Array can be non-zero in rare circumstances
Function Start() As Void
   // Array with int and two dimensions with 3 elements
   // The first dimension starts with 0, the second with 1
   var Numbers := Array.CreateInstance(typeof(Int), <Int>{3,3}, <Int>{0,1})
   For Local dim := 0 UpTo Numbers:Rank - 1
     // PM: Not sure why Numbers:GetLowerBound() does not work
     ? i"LowerBound of Dimension {dim}: {Numbers.GetLowerBound(dim)}"
     ? i"UpperBound of Dimension {dim}: {Numbers.GetUpperBound(dim)}"
   Next

And while we are at it: The rank property gives the number of dimensions of a .Net Array.

Here is an example you have been probably waiting for: An Array with 10 dimensions (I was too lazy to type more and push the compiler to its limit).

Local StrangeArray := Int[,,,,,,,,,]{1,2,3,4,5,6,7,8,9,10} As Int[,,,,,,,,,]
? i"Dimensions={StrangeArray:Rank}"
Dimensions:10

Typed arrays

X# offers a nice language feature that does not exist in both C# and Visual Basic: Typed Arrays. As the name implies, it's possible to define an array where all elements have to have a specific type, like Int32 or Person. Although one could argue that is what generic lists are for, since many developers are used to arrays, especially those with a VO background, a typed array might appear more familiar and is one step towards more type safety at runtime (which is always good).

The declaration for a typed Array of T has the typical developer-friendly syntax that VO developers have known for many years:

Local inventors := {} As Array Of Inventor

Adding elements is done with AAdd() for example:

var inv1 := Inventor{}{Name:="John Backus",Birthday:=DateTime.Parse("3.12.1924"),Inventions:=<String>{"Fortran","an obscure Notation"}}
AAdd(inventors, inv1)

The example is nearly identical to the example in the X# help file (except for the little fact that it does not produce an error;)

Example 9.11: (XS_TypedArray.prg) ** You will find the source code in the repository

Searching arrays

The legendary Donald E. Knuth once (in 1973) wrote a whole book with about 800 pages about searching and sorting algorithms. Luckily, we don't have to worry about the choice and implementation of a search algorithm anymore. This is all handled by the .Net runtime and the VO libraries.

How to search an array of course depends on whether it is a VO or a .Net array. For the first category, there is the universal AScan() function, for the latter there is LINQ (which can be used for VO arrays too).

Searching a VO-Array with AScan

VO has an excellent and rich handling of arrays - especially if an array contains several dimensions. The only requirement is to change the dialect to VO when compiling the code.

VO developers had a powerful search function from the beginning that Visual Basic developers could only have dreamed of, I am talking about the AScan() function of course. It scans an array for a value and returns either its position within the array or 0 (and not -1;).

The following examples are really simple and assume that you have not used this function before but want to use it for VO arrays.

The first example searches for a string value and returns the position.

**Example 9.12: (XS_AscanExample1.prg) **

// Searching a String array with AScan
// compile with xsc .\XS_AScanExample1.prg /dialect:VO /r:XSharp.Core.dll /r:XSharp.RT.dll                               

Function Start() As Void
    Local aTopics := {"SG01","SG02  ", "SG03"} As Array
    // SG02 will be found despite the blank at the end
    Local strTopic := "SG02" As String
    Local Pos := (Int) AScan(aTopics, {|aVal|aVal=strTopic}) As Int
    ? Pos

The next example does the same with integers.

**Example 9.13: (XS_AscanExample2.prg) **

// Searching a int array with AScan
// compile with /dialect:VO /r:RuntimeLibs/XSharp.Core.dll /r:RuntimeLibs/XSharp.RT.dll                               

Function Start() As Void
    Local a1 := {} As Array
    AAdd(a1, 11)
    AAdd(a1, 22)
    AAdd(a1, 33)
    AAdd(a1, 44)
    // Does the value 33 exists in the Array?
    ?AScan(a1, {|x|x = 33})
    // Does the value 55 exists in the Array?
    ?AScan(a1, {|x|x = 55})

The next example shows a little more of the versatility of AScan() and searches a two-dimensional array.

**Example 9.14: (XS_AscanExample3.prg) **

// Scanning a 2d array with AScan until all values found
// compile with /dialect:VO /r:RuntimeLibs/XSharp.Core.dll /r:RuntimeLibs/XSharp.RT.dll                               

Function Start() as Void
  Local AValues := {} As Array
  AAdd(AValues, {"A", 1000})
  AAdd(AValues, {"A", 2000})
  AAdd(AValues, {"B", 100})
  AAdd(AValues, {"B", 200})
  AAdd(AValues, {"A", 3000})
  Local pos := 0 As DWord
  While (pos := AScan(AValues, {|f|f[1]=="A"}, pos+1)) > 0
    ? AValues[pos][2]
  End While

The last example again searches an array with an array as one of its values, but this time all arrays are searched.

**Example 9.15: (XS_AscanExample4.prg) **

// Seaching a two dimensional array with AScan
// compile with /dialect:VO /r:runtimeLibs/XSharp.Rt.dll /r:runtimeLibs/XSharp.Core.dll

Function Start() As Void
   Local a1 := Array{} As Array
   AAdd(a1, {0,0,0,0})
   AAdd(a1, {0,11,22,33,44})
   AAdd(a1, {0,111,222,333,444})
   AAdd(a1, {0,0,0,0})
   // Scan each array in a1 for containing non 0 values
   // ax is Usual, a cast would look like (Array)ax
   ForEach var ax in a1
      ?AScan(ax, {|z|z > 0})
   Next

   // prints 2 because this index in the second array is not 0
   ? AScan(a1[2], {|z|z > 0})

Searching a .Net array

The .Net runtime offers no direct equivalent to AScan(). One reason that X# developers probably won't miss such an equivalent for .Net array is that LINQ offers much more flexibility. Not only for searching but also for sorting arrays and several other things. LINQ will be discussed in chapter 12.

First, a "proof" that LINQ queries are possible with VO-Arrays too.

**Example 9.16: (XS_ArraySearchWithLinq.prg) **

// Searching a VO array with LINQ
// compile with xsc .\XS_ArraySearchWithLinq.prg /r:XSharp.Core.dll /r:XSharp.Rt.dll

using System.Linq

Function Start() As Void
  Local aValues := {} As Array
  AAdd(aValues, {"A", 1000})
  AAdd(aValues, {"A", 2000})
  AAdd(aValues, {"B", 100})
  AAdd(aValues, {"B", 200})
  Var Values := (From w In aValues Where w[1] == "A" Select w[2]):ToList()
  For Local i := 0 Upto Values:Count - 1
      ?Values[i]
  Next

For a .Net-Array there are other ways for searching an array like the (extension) method Where. Since X# does not have a Like operator like Visual Basic, using the IsMatch() method of the Regex class is a good alternative. Don't worry about regular expressions (which are not discussed in this book anyway), the simplest form of a regular expression is just the search term without any ingredients like * or ? (don't ever use * as a placeholder in a regular expression;)).

**Example 9.17: (XS_DotNetArraySearchWithWhere.prg) **

// Searching a .Net array with LINQ

using System.Linq
using System.Text.RegularExpressions

Function Start() As Void
    var cities := <String>{"New York","Rio","Tokyo"}
    var searchTerm := "Tokio"
    var result := cities:Where({c => regex.IsMatch(c, searchTerm)}):ToList()
    ? result:Count
    searchTerm := "Tokyo"
    result := cities:Where({c => regex.IsMatch(c, searchTerm)}):ToList()
    ? result:Count


TIP: If you prefer a like operator in X#, the following article shows how to implement a IsLike() extension method: http://www.blackbeltcoder.com/Articles/net/implementing-vbs-like-operator-in-c. But I recommend using regular expressions because they are so much more flexible, powerful and used in all major programming languages (and not difficult to understand as well).


More operations with arrays

This section contains a few more operations that can be done with arrays.

Sorting a VO Array

If AScan() searches a VO array, what function does the sorting of a VO array? It's ASort() of course. Like AScan it can be used with a Codeblock (Lambda) which makes it very flexible. Since the X# Help contains many examples, I will keep this section as short as possible (but omitting it completely without any example is not an option).

**Example 9.18: (XS_VOArraySorting1.prg) **

// Simple sorting of a VO array with ASort()
// compile with /dialect:VO /r:runtimeLibs/XSharp.Core.dll /r:runtimeLibs/XSharp.Rt.dll

Function Start() As Void
   var cities := {"Plochingen", "Göppingen", "Esslingen", "Reutlingen", "Eislingen"}
   var sortedCities := ASort(cities)
   ForEach var city in sortedCities
     ?city
   Next

The next examples show a few of the muscles the ASort() function has when it comes to sorting a multidimensional array.

**Example 9.19: (XS_VOArraySorting2.prg) **

// Using a lambda expression for sorting a VO array with ASort()
// compile with /dialect:VO /r:runtimeLibs/XSharp.Core.dll /r:runtimeLibs/XSharp.Rt.dll

Function Start() As Void
   var cities := 
   var sortedCities := ASort(cities,,, {|c1,c2| c1[1] > c2[1]})
   ForEach var city in sortedCities
     ?i"{city[2]} has a population of {((Int)city[1]).ToString(""n0"")}"
   Next

Sorting a .Net Array

Sorting a .Net Array is possible of course but each member of the array has to implement the interface IComparable or IComparable. This is the case with all the built-in data types but not with data types based on custom classes.

If each value is "comparable" sorting an array just means to use the static Sort method of the Array class.

**Example 9.20: (XS_ArraySorting1.prg) **

// Sorting a .Net Array - Part 1

Function Start() as void
  Local values := Decimal[]{3} As Decimal[]
  values[1] := (decimal)233.434
  values[2] := (decimal)22 / 7
  values[3] := 567.89M

  Array.Sort(values)

  ForEach var v in values
   ? v
  Next

Sorting is done by the numerical order of each number. This simple scheme won't work of course if the values in the array are based on a custom class like Document. To sort by the value of a property there must be a designated property or a mechanism for providing a lambda (function) that does the sorting. Neither is the case with the Array-Sort-Method.

**Example 9.21: (XS_ArraySorting2.prg) **

// Sorting a .Net Array - Part 2
// Does not work, it raises a InvalidOperationException exception

Class Document
  Property Id As Int Auto
  Property Title As String Auto
End Class

function Start() as void
  Local values := Document[]{3} As Document[]

  values[1] := Document{}{Id := 1000, Title := "X# for ever"}
  values[2] := Document{}{Id := 1001, Title := "C# for ever"}
  values[3] := Document{}{Id := 1002, Title := "F# for ever"}

  Array.Sort(values)

  ForEach var v in values
   ? v:Title
  Next

The error message of the resulting InvalidOperationException says it all: "At least one object has to implement IComparable". As already explained, there has to be a mechanism for defining the sort order.

So, let's implement IComparable (or IComparable< T>) which is much easier than it might appear. There is only one method to implement: CompareTo which expects a single argument.

**Example 9.22: (XS_ArraySorting3.prg) **

// Sorting a .Net Array - Part 3
using System

Class Document Implements IComparable
  Property Id As Int Auto
  Property Title As String Auto

  Method CompareTo(o As Object) As Int
    Return String.Compare(Self:Title, ((Document)o).Title)

End Class

Function Start() as void
  Local values := Document[]{3} As Document[]

  values[1] := Document{}{Id := 1000, Title := "X# for ever"}
  values[2] := Document{}{Id := 1001, Title := "C# for ever"}
  values[3] := Document{}{Id := 1002, Title := "F# for ever"}

  Array.Sort(values)

  ForEach var v in values
   ? v:Title
  Next

For every comparison the Sort method has to make it just calls the implemented CompareTo method which does any kind of comparison necessary and either returns 0 (both values equal), 1 (Self:Title > o: Title), or -1 (Self:Title < o:Title).

Since the object type stays usually the same with every call it's recommended to implement IComparable<T> instead so that the parameter is of type T.

Another glimpse of LINQ

Too lazy to implement interfaces needed for sorting and when using this technique it feels like programming in the 90s again? LINQ comes to your rescue which makes sorting anything that is somehow enumerable easy.


Disclaimer: LINQ, which was introduced with .Net 3.51 in the year 2008, is already 15 years old but it still feels new and shiny. And it's not an acronym (I think, although something like 'Language Integrated (Language) Neutral Queries' would fit).

**Example 9.23: (XS_ArraySortingLINQ.prg) **

// Sorting a .Net Array with LINQ
using System
using System.Linq

Class Document
  Property Id As Int Auto
  Property Title As String Auto
End Class

function Start() as void
  Local Documents := Document[]{3} As Document[]

  Documents[1] := Document{}{Id := 1001, Title := "C# for ever"}
  Documents[2] := Document{}{Id := 1002, Title := "F# for ever"}
  Documents[3] := Document{}{Id := 1000, Title := "X# for ever"}

  var sortedDocuments := From d in Documents OrderBy d.Id Select d

  ForEach var d in sortedDocuments 
   ? i"{d.Title} (Id={d.Id})"
  Next

LINQ will be covered in more detail in chapter 12 of this book so I will omit any explanations at this point. Except for this one: Sorting with LINQ always works with plain objects that only contain some properties. These objects can be part of an array, a list, a dictionary, or something else. The X# help has some very good LINQ examples.

Deleting elements in a VO array

Deleting something in an array would be a little bit challenging if this had been done without a function. For VO arrays ADel() is the function to use.

Example 9.24: (XS_ADelExample1.prg) ** You will find the source code in the repository

Interestingly deleting an element from a .Net array is much more complicated. Since the Array class offers no Remove method, deleting an element would mean excluding the elements to remove while copying all other elements into a new array. I will not provide an example because it makes more sense to use a list class like List that offers both a Remove() and a RemoveAt() method.

Lists

Like an array, a list can hold any number of elements, but instead of having to rely on runtime functions or operators, the class of the list is based on offers methods like Add or Remove. Another name for lists in the Microsoft universe is collections although a "collection" class with this particular name does not exist. There are two important namespaces in the .Net runtime with list classes: System.Collections and System.Collections.Generic. The most common non-generic list class is the ArrayList class which is simple to use. The most common generic list class is List which is also really simple to use.

The following command creates a new list that can hold any kind of value:

var names := ArrayList{}

Like Arrays lists can be initialized too. The syntax for X# closely resembles the C# syntax except for an extra pair of curly braces:

```var actors := List{}{"Robin Williams", "Michael J. Fox", "Bruce Willis", "William Shatner"} var actors := ArrayList{}{1234, "Code no more", Random{}, DateTime.Now}


There are many operations possible with lists like inserting, deleting, updating, searching elements, etc. I won't discuss them in this book because there are literally "myriads" places on the Internet that explain these data structures and algorithms in great detail. Any technique and code sample for C# can be applied 1:1 to X# as well.

### Generic Lists

A generic list is a list that only accepts values of a specific kind. The general syntax *List<T>*, where T stands for a type name, is the typical syntax for any generic list or dictionary. I usually prefer generic lists over non-generic lists.

The following command creates a new generic list that can hold only strings:

````
var names := List<String>{}
````

Initializing a generic list is no different from initializing a non-generic counterpart:

```var actors := List<String>{}{"Robin Williams", "Michael J. Fox", "Bruce Willis", "William Shatner"}
var actors := List<String>{}{"Robin Williams", "Michael J. Fox", "Bruce Willis", "William Shatner"}

Each element of a generic list is accessed with an index that starts with 0:

actors[1]

Since a list can contain other lists, the index syntax is indistinguishable from accessing a two-dimensional array:

?i"In Back to the Future part 2 Jennifer Parker is played by {movies[1][2]}"

Example 9.25: (XS_GenericListOfLists.prg) ** You will find the source code in the repository


NOTE: Its worth mentioning that the X# array type internally uses a List\ to store the values, so it is NOT a .Net array. Because it's a List<>, an X# array can dynamically grow and shrink.


Interfaces as list elements

It might seem a little unusual at first that interfaces can used as a data type for generic lists.

For example.

Local vehicles := List<IVehicle>{} As List<IVehicle>

It's important to understand that an interface type is a (data) type like any other type although it just contains the method signatures but not any implementation.

A variable with an interface type can be assigned to any object that implements that interface.

Grouping list classes by their interfaces

The .Net runtime offers about two dozen different kinds of list classes. So many choices. Whoever might need a PriorityQueue class? A good way of getting a better overview is by grouping the classes by the interfaces they implement. Most collection classes implement several interfaces. The often-used List class implements not only one but a total of eight interfaces:

  1. IList`1
  2. IList
  3. ICollection`1
  4. ICollection
  5. IEnumerable`1
  6. IEnumerable
  7. IReadOnlyList`1
  8. IReadOnlyCollection`1

The `1 stands for one generic type.

The implemented interfaces define the capabilities of a list. An interface that every list needs is IEnumerable or IEnumerable for a generic list. Without the GetEnumerator() method of that interface, there would be no iteration possible with the ForEach command. Without the IList interface and their members add and remove it won't be possible to add and remove members. So if any listed class implements the IList interface, the add and the remove member is warranted by this contract.


TIP: Implementing your list classes that behave like regular list classes by implementing the needed interfaces is a very good learning exercise. The official .Net documentation contains a very good example of this. Have a look here https://learn.microsoft.com/en-us/dotnet/api/system.collections.ienumerable.


Thanks to .Net reflection it's really simple to list the names of the interfaces any class implements in X# too. You only have to call the GetInterfaces() method of the type object that contains all the type metadata of an object.


Example 9.26: (XS_GenericListInterfaces.prg) ** You will find the source code in the repository

Alt Each list class implements several interface Fig 9.2: Each list class implements several interfaces

Dictionaries

A dictionary is a special kind of list where every value is accompanied by another value - the key. The key (value) is used to access value instead of a numeric index. The key can be any kind of value, an int, a string, an object, or even another dictionary.

Dictionaries offer several advantages over two-dimensional arrays:

  1. More flexibility
  2. Better performance (with a large number of pairs)
  3. Make code better readable

There may be even more;) I would prefer a dictionary over a 2D array most of the time because of its performance and readability.

The .Net runtime offers simple dictionary classes like Hashtable and generic dictionaries like Dictionary<S,T> where S denotes the type of the key and T the type of the value.

The following examples use a dictionary based on a Hashtable class by storing CityInfo objects and making each accessible by their "Kfz-Kennzeichen".

The dictionary is instantiated without any further details about the key-value-pairs:

var cities := Hashtable{}

A key-value-pair is either added by the Add() method or by assigning the value through the pair directly

cities:Add("ES", CityInfo{}{Name:="Esslingen", Population:=89000})

cities["S"] := CityInfo{}{Name:="Stuttgart", Population:=700000}

In the first example, the key is "ES", and in the second example "S". It's important to understand that the key can be any type of value, not only string or int. It can be an object or even another hashtable.

Enumerating a dictionary means enumerating the keys and accessing each value through the key.

Example 9.27: (XS_Dictionary.prg) ** You will find the source code in the repository

And now: generic Dictionaries

X# uses (generic) dictionaries much like C# does. Instead of one the definition of a generic dictionary uses two type placeholders like S for the key and T for the value.

var cities := Dictionary<S, T>{}

Even the initialization of a Dictionary<S,T> with values is possible.

The following example resembles the last example except that this time a generic _Dictionary<String,CityInfo>_is used with a key of type String and a value of type CityInfo. All values are then printed again in a loop by using the key.

Example 9.28: (XS_GenericDictionary1.prg) ** You will find the source code in the repository

The next example initializes the dictionary with key-value pairs with its instantiation. Again, a big compliment to the X# developers to make this possible.

**Example 9.29: (XS_GenericDictionary2.prg) **

// Example for using a Dictionary<T,S> 

using System.Collections.Generic

Class CityInfo
  Internal Property Name As String Auto
  Internal Property Population As Int Auto

  Public Method ToString() As String
     Return i"City: {Self:Name} Population: {Self:Population}"

End Class

Function Start() As Void
    Local cities := Dictionary<String, CityInfo>{} {;
        {"ES", CityInfo{}{Name:="Esslingen", Population:=89000}},;
        {"PL", CityInfo{}{Name:="Plochingen", Population:=34000}},;
        {"GP", CityInfo{}{Name:="G�ppingen", Population:=45000}};
        {"S", CityInfo{}{Name:="Stuttgart", Population:=45000}};
        } As Dictionary<String, CityInfo>
    ForEach k As String In cities:Keys
        ? cities[k]
    Next

Getting keys and values

Another nice thing about dictionaries is that there is no need for extra functions, it's all built-in. Like querying for keys and values. And the null operator ? can be helpful too.

The ContainsKey() method checks for a key, the ContainsValue() method for a value. There is no equivalent for checking for both a key and a value but the TryGetValue() method comes close.

   var d1 := Dictionary<String, Logic>{}
   Local dValue As Boolean
   if d1:TryGetValue("K1", out dValue)
     ? i"K1: {dValue}"
   Else
     ? "No Value for K1"
   EndIf

Accessing a variable that might be null would normally result in a NullReferenceException. This can be avoided with the ? operator:

d1?:Add("K1", True)

If d1 is null nothing will happen.

If a key does not exist, a KeyNotFoundException exception is the result. There is no way to avoid this exception but to use the ContainsKey() method.

Example 9.30: (XS_DictionaryKeysAndValues.prg) ** You will find the source code in the repository