Chapter 12

LINQ

LINQ had been introduced with Visual Studio 2008 and .NET 3.5.1. One of the main creator of LINQ was no other than computer programming languages legend Anders Hejlsberg, inventor of Turbo Pascal, Delphi and one of the architects of C# and later of TypeScript_too. When _Anders came to Microsoft in 1996, his first task was to port an existing Java UI library to Windows which would become later WinForms within the .Net Framework.

The idea behind LINQ

The revolutionary idea of LINQ was to abolish the "impedance" between code and data. That means that developers will be able to fluently combine code with data for queries against any kind of list without having to resort to any kind of method calls.

Take for example the filtering of a list of strings.

Without LINQ it would require the call of a filter() method.

var cities = String[]{"Esslingen", "Plochingen", "Stuttgart"}
var smallCities = filter(cities, "*.gen")

With LINQ the filtering can be combined with a list in a more readable statement:

var cities := List<String>{}{"Esslingen", "Plochingen", "Stuttgart"}
var smallCities := From City In Cities Where City.EndsWith("gen") Select City

This time the filtering is part of the syntax and not done by function. What is more important is that filtering is only one of many options. LINQ queries can be long.

Although LINQ is about querying lists and arrays, updating elements of a list or creating new elements is possible as part of the query.

LINQ and SQL

Although the syntax in the second line looks like SQL, LINQ has nothing to do with SQL (there had been already endless debates about the fact that a LINQ query starts with From and not with Select for example).

It's important to note that the result of a query is not an array or a list. It's a special System.Linq.Enumerable+WhereListIterator`1 object that can be enumerated like a typed list. The fact that any LINQ query is just the definition of the query and not the result is called lazy evaluation.

And City is just the name of a local variable that derived its type from the fact that Cities is a List.

Example 12.1: (XS_LinqExample1.prg) ** You will find the source code in the repository

The LINQ syntax is not the only option of course. Since the "magic keywords" like Where and Select are based on extension methods, they can be called directly of course.

**Example 12.2: (XS_LinqExample2.prg) **

// Using LINQ with extension methods

using System.Collections.Generic
using System.Linq

Function Start() As Void
   var cities := List<String>{}{"Esslingen", "Plochingen", "Stuttgart"}
   var smallCities := cities:Where({city => city.EndsWith("gen")}):Select({city => city})
   ForEach var city in smallCities
      ? city
   Next

Which one is better? Although it is a matter of taste, of course, I would prefer the second option because it follows more the traditional thinking of calling methods that return something (even its the same iterator object).

The main advantage of the Where extension method (and LINQ in general) over functions like AScan is that the methods always return typed objects. That's the reason why Visual Studio offers IntelliSense when working with the returned objects.

Don't forget to include the System.Linq namespace. Otherwise, the where extension method cannot be found by the compiler.

LINQ and databases

What about databases? Wasn't LINQ supposed to be some kind of "object layer" above the database API? As already stated, LINQ itself has nothing to do with databases.

There used to be a "LINQ for SQL" when LINQ came out in 2008 but Microsoft axed the project in favor of the Entity Framework which is an ORM (Object Relational Mapper) outside the .Net runtime. The Entity Framework is the official ORM from Microsoft and is an integral part (or maybe better an addition) of .Net 7 and beyond.

But of course, LINQ can the used to query datatables and their rows. But since each field in a row is not typed the query loses a little bit of its simplicity.

For the next example imagine a tiny DataTable that contains a few (data) columns. A query is needed that returns all columns with a name that starts with an 'A'.

**Example 12.3: (XS_DataTableLinq.prg) **

// Example for selecting rows with LINQ

using System.Data
using System.Linq

Function Start() As Void
  var ta := DataTable{"Cars"}
  ta:Columns:Add(DataColumn{"Id", typeof(Int)})
  ta:Columns:Add(DataColumn{"Name", typeof(String)})
  ta:Columns:Add(DataColumn{"Year", typeof(Int)})
  ta:Columns:Add(DataColumn{"Country", typeof(String)})
  ta:Columns:Add(DataColumn{"Price", typeof(Double)})
  var r := ta:NewRow()
  r["Id"] := 1
  r["Name"] := "De Tomaso P72"
  r["Year"] := 2019
  r["Price"] := 1.3E6
  r["Country"] := "Italy"
  ta:Rows:Add(r)
  r := ta:NewRow()
  r["Id"] := 2
  r["Name"] := "La Ferrari"
  r["Year"] := 2013
  r["Price"] := 1.4E6
  r["Country"] := "Italy"
  ta:Rows:Add(r)
  r := ta:NewRow()
  r["Id"] := 3
  r["Name"] := "Aston Martin Vulcan"
  r["Year"] := 2014
  r["Price"] := 2.3E6
  r["Country"] := "England"
  ta:Rows:Add(r)
  r := ta:NewRow()
  r["Id"] := 4
  r["Name"] := "Koenigsegg Gemera"
  r["Year"] := 2020
  r["Price"] := 2.3E6
  r["Country"] := "Sweden"
  ta:Rows:Add(r)

  // var rows := from row as DataRow in ta:rows where row["Country"]:ToString() == "Sweden" select row
  var rows := from row in ta:rows:Cast<DataRow>() where row["Country"]:ToString() == "Sweden" select row
  ForEach row As DataRow in rows 
   ? i"The {row[""Name""]} is from {row[""Country""]}"
  Next

  rows := from row as DataRow in ta:Rows where row["Country"]:ToString() =="Italy" && Double.Parse(row["Price"]:ToString()) > 1300000 select row
  ForEach row As DataRow in rows 
   ? i"The {row[""Name""]} costs {row[""Price""]}"
  Next

Since all DataColumns are part of the System.Data.DataColumnCollection for which the where extension method does not apply, the compiler cannot determine the type of d so that in X# the variable has to be declared explicitly.

Not a big deal of course. Note that the columns will be returned sorted by their column name. That's cool.

LINQ and WinForms

Although there is of course no official connection between LINQ and WinForms, WinForms controls are just another kind of object, LINQ queries can be really helpful for selecting controls based on their type or any other attribute.

The following example lists all controls that are child controls of a GroupBox control based on their type.

The query, that is part of the example, is hopefully not very difficult to understand:

(FROM c AS Control IN ((GroupBox)container ):Controls:OfType<Label>() SELECT (Label)c):ToList()

This LINQ query returns all label controls that are child controls of a GroupBox as a generic List - but of what type?

Example 12.4: (XS_WinFormsContainerControlsLINQ.prg) ** You will find the source code in the repository

Practicing LINQ with LINQPad

For anybody who likes to "dive deeper" into LINQ and all the possibilities that come with it, LINQPad by Joe Albahari is a kind of must-have tool because it makes working with LINQ queries simple and rewarding.

The main advantage of LINQPad is querying the database over a connection that had been set up first.

LINQPad is free but there is also a "premium version" which I recommend (at least to support the developer of this really helpful tool)

LINQPad can process statements and expressions in C#, F#, and VB but not in X# (maybe someone will develop an extension in the future). This is not a disadvantage since the X# development stayed as close as possible to the C# syntax.

Last but not least: There are some really good LINQ examples in the X# help file. The current version of LINQPad is 7 but the tool was a great assistant from the first version on.

Alt LINQPad makes trying out LINQ queries very easy and convenient Fig 12.1: LINQPad makes trying out LINQ queries both easy and convenient and is a great learning tool

A (very short) look behind the scenes

LINQ is based on a few (simple) ingredients provided by the X# compiler:

  • Type inference for local variables
  • Extension Methods
  • Lambdas
  • Anonymous types
  • Query comprehensions

Type inference for local variables

The compiler can interfere with the type of a local variable through the assignment so there is no need for a type (that's how var works)

Extension Methods

Any method can attached to a class without having to extend the class definition itself. A very handy feature.

Lambdas

A function is just the body - there is no need for a formal declaration.

Anonymous types

A type can be created without a formal type definition like a class definition.

Query comprehensions

Statements like From, Where, Select, etc can be used as part of the source code without being a formal part of the language definition itself.

There are nearly 40 extension methods mostly for Enumerable (Where and Select are just two of them). It would be a real "Fleissarbeit" to put them all together. And it won't be necessary because the Microsoft docs contain them all and there are hundreds of Websites and thousands of entries on SO that explain every detail of LINQ. Of course, 99% of the examples are C#. This section should give you enough information to transform any C# example into X#.

Lazy evaluation

One key feature of LINQ is called lazy evaluation. That means that a LINQ query defines only the query, the expression tree. The compiler translates the query into an expression tree. Only when the query is used inside a loop or a ToList() method is appended, the query will be run and therefore the CPU burns cycles.

Method What does it?
Select Selects the "output" object
Where Filters the objects based on a lambda expression
SelectMany Select the output object from multiple clauses
OrderBy Sorts the objects
Cast A type conversion with each object
GroupBy Groups objects
Join joins two or more lists

Tab 12.1: A few of the "LINQ methods" that are extension methods of IEnumerable and IQuerable.

LINQ and debugging

One of the early criticisms of LINQ was the minimal debugging support in Visual Studio. Even a complex query could only be debugged in a single step. This has improved a lot since the first version. In recent versions of Visual Studio parts of a LINQ statement can be debugged by pressing [F11] which makes debugging (and understanding) a lot easier.

LINQ examples

The best way to explain LINQ and show its potential for the simplification of common techniques like filtering, sorting or grouping is by showing small examples.

Iterating through all elements of a list but the last one

X# is not Python (the language) so there is no slicing syntax which makes accessing elements of a list very simple (and not every part of the C# syntax is already part of X#). So iterating over all elements of a list that is provided by the method would mean an extra step of assigning the elements to a variable, using the index notation and iterating over the variable. With LINQ it becomes a little easier or different, depending on your point of view.

**Example 12.5: (XS_IteratingListExeptLastElement.prg) **

// Iterating over a list except the last element

using System.Collections.Generic
using System.Linq

Class C
  Static Method GetNumbers() As List<Int>
    Return List<Int>{}{11,22,33,44,55,66}

End Class

Function Start() As Void
   ForEach var z in C:GetNumbers():ToArray():Reverse():Skip(1):Reverse():ToList()
       ? z
   Next

Now for some readers, the following statement might seem a little like a joke:

ForEach var z in C:GetNumbers():ToArray():Reverse():Skip(1):Reverse():ToList()

Why do I have to chain five (!) methods together just to achieve something that could be done with a simple loop? And what about the performance if this would be a very large list? I know that many experienced developers will have a problem with this approach and I am with you. The reason for having to turn the List into an array is that the Reverse() method of a generic collection does an "in place reverse" and returns nothing. At the end, the array should be a list again.

So this example is more like an example of the flexibility that comes with LINQ if needed. And it's always good to have options.

With .Net Core there is a SkipLast() method and I am sure that with X# 3.0 (or beyond) X# will have the range operator like in C#.

Converting a CSV file into objects

A CSV file is a "Comma Separated Values" text file although the separator does not have to be a comma and could be any valid character. CSV is still a very popular text file format for exporting data because of its simplicity.

The following examples show how easy it is to convert a CSV file line by line into objects of a certain class with a LINQ query. The text file is a file named 'Albumtitles.txt' that consists of several rows:

Title,Interpret,Year
Boys And Girls,Bryan Ferry,1985
Nightshift,Commodores,1985
Brothers in Arms,Dire Straits,1985

**Example 12.6: (XS_ConvertText2Objects.prg) **

// Using LINQ to convert a Csv file into objects

Using System.IO
Using System.Linq

Class Album
  Property Title As String Auto
  Property Interpret As String Auto
  Property Year As Int Auto
  Override Method ToString() As String
    Return i"Title: {Self:Title} Interpret: {Self:Interpret} Year: {Self:Year}"

End Class

Function Start() As Void
   var txtPath := Path.Combine(Environment.CurrentDirectory, "Albumtitles.txt")
   // really "cool" how easy it is to skip first line with the header names
   var albums := from line in File.ReadAllLines(txtPath):Skip(1) Select Album{}{title:=line:split(c",")[1],;
                                                                              interpret:=line:split(c",")[2],;
                                                                              year:=Int32.Parse(line:split(c",")[3])}
   ForEach var album in albums
    ? album
   Next


TIP: This is a very nice website for playing around with CSV data in a browser where everything stays in the browser and will not send over the Internet: https://whattheduck.incentius.com. The website uses DuckDB which allows SQL queries on any CSV file in the browser. It has nothing (or little) to do with X# though, but it proves again there is always something new that nobody has invented before.


Selection Controls based on their types

Of course, there is no "LINQ for WinForms" in the .Net runtime. But if you already understand the idea of LINQ, you know that such a direct connection is not necessary at all. LINQ queries work with any Array or List (with a collection it depends on the implemented interfaces).

What about querying WinForms controls, that are part of a container, based on their type? Like getting only labels? Or buttons with specific property values? Using a LINQ query is more elegant, simpler, and in the end better readable than a traditional loop.

The following query returns all controls that are child controls of a GroupBox and are Labels:

(FROM c AS Control IN ((GroupBox)container ):Controls:OfType<Label>() SELECT (Label)c):ToList()

Would a Where() method be simpler and better readable? In theory. It may be surprising at first, that the following query does not work:

(((GroupBox)container):Controls):Where({c => c IS Label}):ToList()

The compiler error is clear: A ControlCollection has no Where method. It took me a few seconds to search StackOverflow to know what the reason was (I should have known anyway). The ControlCollection does not implement IEnumerable. This does not mean though, that the Where()-method cannot be used. There is just an extra type conversion (thanks to a knowledgeable developer) necessary so that the Where() method can be applied:

((GroupBox)container):Controls:Cast<Control>():Where({ c => c IS Label}):ToList()

Maybe at this point, you are just happy that you have 100% confidence in the fact, that a traditional loop and an if statement always work. But, I can only encourage you to give LINQ another try.

Ex

Advanced LINQ

There is more to LINQ than a simplified querying syntax for arrays and lists and extension methods for IEnumerable and IQueryable. It's possible, with little effort, to construct a expression tree from scratch and compile it into a query or query the elements of a LINQ statement with the Visitor pattern.

And finally: create your expression trees for more flexibility

By now it should be clear that LINQ is both a very flexible and developer-friendly extension for dealing with any kind of lists and arrays. But all the examples had one thing a common, they used a fixed expression. In some scenarios it would be more flexible with the expression could be constructed dynamically. This is possible too by creating a custom expression tree that is compiled into a LINQ expression that can be used the same way as any other LINQ expression. While this may sound complicated, in the end, it isn't. As usual, there are so many examples available that be used both for learning or taken 1:1 for a specific requirement.

Some examples will follow in one of the future releases of this chapter.