---
marp: true
paginate: true
math: mathjax
theme: buutti
title: N. Generics, IEnumerable and LINQ
---

# Generics, IEnumerable and LINQ

<!-- headingDivider: 5 -->
<!-- class: invert -->

## Generics

* When using lists, you have to define the type of data that will be stored in the list, inside the angled brackets:
  ```csharp
  List<int> numberList = new List<int>();     // This list stores variables of type int
  ```
* This means that list is a  *__generic__*  class: it can contain data of any type
* Classes, structs, interfaces and methods can also be generic
* All the variables that are defined generic inside a generic container will be assigned a type only when the containing object/method is called

### Creating a generic class

```csharp
class GenericClassExample<T>
{
  public T value;    // This value will be whatever type is specified at instantiation
  public void PrintTypeAndValue()
  {
    Console.WriteLine
      ($"This class contains a variable of type {value.GetType()} and of value {value}");
  }
}
class Program
{
  static void Main(string[] args)
  {
    GenericClassExample<int> example = new GenericClassExample<int>();
    example.value = 20;
    example.PrintTypeAndValue();
  }
}
```
![](imgs/13%20Generics%2C%20IEnumerable%20and%20LINQ_1.png)

### Multiple type parameters

* Generic classes can receive multiple types as parameters:
  ```csharp
  class CustomContainer<T1, T2, T3>
  {
    public T1 First { get; set; }
    public T2 Second { get; set; }
    public T3 Third { get; set; }
  }
  class Program
  {
    static void Main(string[] args)
    {
      CustomContainer<int, string, DateTime> container
        = new CustomContainer<int, string, DateTime>();
      container.First = 10;
      container.Second = "Testing.";
      container.Third = DateTime.Now;
    }
  }
  ```

### Creating a generic Method

```csharp
void GenericMethodExample<T>(T value)
{
  Console.WriteLine
    ($"This method was passed a variable of type {value.GetType()} and of value {value}.");
}
GenericMethodExample<string>("ABC");
```

![](imgs/13%20Generics%2C%20IEnumerable%20and%20LINQ_2.png)

* ***Note:*** You could name the generic type as anything, e.g. `<GenericType>`. It is named `<T>` here by convention.

## Exercise 1: Initializing a populated list
<!-- _class: exercise -->

Create a generic method `GetPopulatedList<T>` which takes two parameters: `T value` and `int length`, and returns a new list of type `T` which is populated with the `value` variables and has a length of `length`.

Test your method out with a couple of different types and lengths:

```csharp
List<string> list = GetPopulatedList<string>("Hello, there", 10);
foreach(string value in list)
{
    Console.WriteLine(value);
}
```

## IEnumerable

* Lists and arrays are both  *__collections__* that implement the  `IEnumerable` interface
* All objects that implement the IEnumerable interface can be iterated with the foreach statement
  ```csharp
  IEnumerable<string> names = new string[] {"Harry", "Luke", "Harley"};
  IEnumerable<string> days = new List<string> {"Sunday", "Monday", "Friday"};

  foreach (string name in names)
    Console.WriteLine(name);
  
  foreach (string day in days)
    Console.WriteLine(day);
  ```

---

* The IEnumerable interface itself doesn't hold much functionality:
  ![](imgs/13%20Generics%2C%20IEnumerable%20and%20LINQ_3.png)
* However, the LINQ library includes all the methods you would typically need to apply to IEnumerables, such as filtering

## LINQ

### The problem

* Here's an example of a common *__query__* a programmer might have to do:
* Try to find an object with a certain id from an array

  ```csharp
  IEnumerable<User> userArray = new User[2]     // Initialize a new array of users
  {
    new User { Id = 0, Name = "Rene" } ,
    new User { Id = 1, Name = "Ville" }
  };

  User FindObjectWithId(int id)
  {
    foreach (User user in userArray)
    if (user.Id == id)
      return user;
    return null;
  }
  Console.WriteLine(FindObjectWithId(1).Name);    // Outputs "Ville"
  ```

---

* Having to write your own method for every possible query operation (select, filter, sort…) would be nonsensical
* The `List` class includes some methods for manipulation, but...
  * These only work on lists; not all `IEnumerables` (e.g. arrays) contain those methods!
* Not to mention different data types altogether (objects, SQL databases, XML, JSON…)
* What to do?

### The solution

* To introduce extensive query capabilities to all collection types, Language-Integrated Query (LINQ) was created
* LINQ supports querying of objects and even XML and SQL data, directly in your code
* Get started by adding the [System.Linq](https://learn.microsoft.com/en-us/dotnet/api/system.linq?view=net-9.0) namespace to your project:
  ```csharp
  using System.Linq;
  ```

---

* Here's a solution to the earlier example that uses LINQ:
  ```csharp
  IEnumerable<User> userArray = new User[2]      // Initialize a new array of users
  {
    new User { Id = 0, Name = "Rene" } ,
    new User { Id = 1, Name = "Ville"}
  };
  Console.WriteLine(userArray.First(user => user.Id == 1).Name);    // Outputs "Ville"
  ```
* The [Enumerable.First](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.first?view=net-9.0) method returns the first result that satisfies the expression in the parameters

* The arrow syntax above is called a  *__lambda expression__*

## Lambda expressions

* Lambda expressions are a quick way of writing one-line methods
* The `First` method of LINQ takes a  [delegate](11.%20Delegates%20and%20Events.md#delegates) as a parameter
* We could just declare a method beforehand, and then refer to it in `First` like this:
  ```csharp
  bool GetUserWithId(User user)
  {
    return user.Id == 1;
  }
  Console.WriteLine(userList.First(GetUserWithId).Name);    // Outputs "Ville"
  ```
* To shorten the expression, we could use a delegate inside the parameters to refer to an anonymous method:
  ```csharp
  Console.WriteLine(userList.First(delegate (User user)
    { return user.Id == 1; }));    // Outputs "Ville"
  ```

### Lambda expressions compared

Using LINQs "First" -query with...
<div class='columns111' markdown='1'>
<div markdown='1'>

...a method:
```csharp
bool GetUserWithId(User user)
{
  return user.Id == 1;
}
Console.WriteLine(
  userList.First(
    GetUserWithId).Name);
// Outputs "Ville"
```

</div>
<div markdown='1'>

...an anonymous method:

```csharp
Console.WriteLine(userList.First(
delegate (User user)
{
  return user.Id == 1;
}));
// Outputs "Ville"
```

</div>
<div markdown='1'>

...a lambda expression:

```csharp
Console.WriteLine(userList.First(
  user =>
    user.Id == 1).Name);
// Outputs "Ville"
```

</div>
</div>

### Lambda expressions: An example

Regular methods can also be declared using the arrow function

```csharp
static void Main(string[] args)
{
  // Method body assigned with lambda expression
  string PrintCheckUpper(bool upper, string text) =>
    upper ? text.ToUpper() : text;
  
  PrintCheckUpper(true,  "I'm not angry!");    // Outputs I'M NOT ANGRY!
  PrintCheckUpper(false, "I'm not angry!");    // Outputs I'm not angry!
}
```

### LINQ and Lambda expressions

* Going back to our LINQ example...
  ```csharp
  IEnumerable<User> userArray = new User[2]      // Initialize a new array of users
  {
    new User { Id = 0, Name = "Rene" } ,
    new User { Id = 1, Name = "Ville"}
  };
  ```
* Here's a solution without lambda expressions:
  ```csharp
  bool GetUserWithId(User user)
  {
    return user.Id == 1;
  }
  Console.WriteLine(userArray.First(GetUserWithId).Name);    // Outputs "Ville"
  ```
* And here's the shorter version with lambda expression:
  ```csharp
  Console.WriteLine(userArray.First(user => user.Id == 1).Name);    // Outputs "Ville"
  ```

### LINQ methods

* LINQ contains methods for filtering, ordering, grouping, joining and selecting
* Suppose we have a class `Person` that contains a property `Country`
* The following performs filtering to the `persons` object, returning only the persons whose country is of value "Finland":
  ```csharp
  List<Person> persons = new List<Person> {/* Insert data here */};

  var queryResult = persons
    .Where(person => person.Country == "Finland");
  ```

### LINQ methods listed

| Method  | Example                                      | Description                                                         |
|:--------|:---------------------------------------------|:--------------------------------------------------------------------|
| Where   | `persons.Where(p => p.Country == "Finland")` | Filters results based on an expression                              |
| OrderBy | `persons.OrderBy(p => p.LastName)`           | Orders results based on one of its properties                       |
| Select  | `persons.Select(p => $"Dr. {p.LastName}")`   | Converts the enumerable into an another type based on an expression |

---

| Method  | Example             | Description                        |
|:--------|:--------------------|:-----------------------------------|
| Skip    | `persons.Skip(1)`   | Skips first N elements             |
| Take    | `persons.Take(5)`   | Returns N elements                 |
| ToList  | `persons.ToList()`  | Converts `IEnumerable` to a list   |
| ToArray | `persons.ToArray()` | Converts `IEnumerable` to an array |

---

| Method         | Example                                      | Description                                                                            |
|:---------------|:---------------------------------------------|:---------------------------------------------------------------------------------------|
| Any            | `persons.Any(p => p.Country == "Finland")`   | Return `true` if at least one element satisfies a condition                            |
| All            | `persons.All(p => p.Age >= 18)`              | Return `true` if all elements satisfy a condition                                      |
| FirstOrDefault | `persons.FirstOrDefault(p => !p.Active)`     | Returns the first element that satisfies a condition, or `null` if not found           |
| Count          | `persons.Count(p => p.FirstName == "Mauri")` | Returns the count of elements that satisfy a condition, can be left blank to count all |

More LINQ methods can be found  [here](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable?view=net-7.0)

### LINQ: A chained example

* Suppose we have a csv file `authors.csv` in our project directory.
  ```csharp
  // using System.IO;
  string path = @"C:\some\path\authors.csv";
  var authors = File.ReadAllLines(path)
    .Skip(1)         // Skip the first line which contains the column titles
    .Select(line =>  // Using lambda expression, return the new Author objects
    {
      var columns = line.Split(',');
      return new Author
      {
        Id = int.Parse(columns[0]),
        Name = columns[1],
        Description = columns[2]
      };
    })
    .ToList();       // Turn the resulting IEnumerable into a list
  ```
* `authors` now contains all the authors from the original csv file!

## Query syntax

* An alternative way of using LINQ is with the  *__query syntax__*
* The following performs filtering to a `persons` object:
  ```csharp
  var queryResult = from person in persons
    where person.country == "Finland"
    select person;
  ```
* The  *__range variable__* `person` works like the range variable in a `foreach` loop in that it holds a copy of the current element in the `persons` variable
* The `where` keyword specifies the condition for the filter

### LINQ queries: An example

* Suppose we have a class City that contains a property Description
* The following would print the descriptions of all cities that have descriptions less than 100 characters long
  ```csharp
  IEnumerable Cities = new List<City> {/* Insert data here */};

  var filteredResult = from city in Cities
    where city.Description.Length < 100
    select city;

  foreach (City result in filteredResult)
    Console.WriteLine(result.Description);
  ```

## Extension methods vs. query syntax

* In the previous examples, we used the query syntax of LINQ
* Both the methods and queries do pretty much the same thing
* It is up to you which syntax you want to use
  * The method syntax works like any normal C## methods
  * The query syntax might be more approachable to those who are familiar with SQL

<div class='columns' markdown='1'>
<div markdown='1'>

```csharp
var methodResult = persons
  .Where(person => 
    person.Country == "Finland");
```

</div>
<div markdown='1'>

```csharp
var queryResult = from person in persons
  where person.Country == "Finland"
  select person;
```

</div>
</div>

* All queries listed here:  [https://www.tutorialsteacher.com/linq/linq-standard-query-operators](https://www.tutorialsteacher.com/linq/linq-standard-query-operators)

## `ToArray()` and `ToList()` methods

* Notice that the LINQ queries return an `IEnumerable`
* If you need to use arrays or lists, you need to call the `ToArray()` or `ToList()` methods

<div class='columns32' markdown='1'>
<div markdown='1'>

  ```csharp
  string[] strings = new string[] 
    { "Timo", "Pekka", "Taina", "Kalle" };
  string[] queryResult = strings
    .Where(s => s.StartsWith('T'));
  ```
![](imgs/13%20Generics%2C%20IEnumerable%20and%20LINQ_4.png)

</div>
<div markdown='1'>

```csharp
string[] strings = new string[]
  { "Timo", "Pekka", "Taina", "Kalle" };
string[] queryResult = strings
  .Where(s => s.StartsWith('T'))
  .ToArray();
```

No error!

</div>
</div>




## Exercise 2: Filtering Names
<!-- _class: exercise -->

* Download this file of names and add it to your project folder:  [https://raw.githubusercontent.com/dominictarr/random-name/master/names.txt](https://raw.githubusercontent.com/dominictarr/random-name/master/names.txt)
* Read all the contents into a string array with `File.ReadAllLines()`
* Create a main loop where the user is asked for a string. Print the total number of names which contain that string.
* If there are less than 10 resulting names, print the names as well!

## Exercise 3: Queries on Object Lists
<!-- _class: exercise -->

* Expand on the exercise 2.
* Create a new class User with two properties, int Id and string Name
* If the number of filtered names is less than 10, create a list of Users with those names and a running Id
* Sort the list of users by the length of the Name property
* Print the names and id:s of the users in the sorted list

## Going Further: Extension Methods
<!-- _class: extra -->

* Recap: `IEnumerable` itself only contains one method
* How does the LINQ library suddenly add all these methods to our Enumerables?
* This is possible with [extension methods](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods)!