--- marp: true paginate: true math: mathjax theme: buutti title: N. Generics, IEnumerable and LINQ --- # Generics, IEnumerable and LINQ ## 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 numberList = new List(); // 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 { 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 example = new GenericClassExample(); 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 { public T1 First { get; set; } public T2 Second { get; set; } public T3 Third { get; set; } } class Program { static void Main(string[] args) { CustomContainer container = new CustomContainer(); container.First = 10; container.Second = "Testing."; container.Third = DateTime.Now; } } ``` ### Creating a generic Method ```csharp void GenericMethodExample(T value) { Console.WriteLine ($"This method was passed a variable of type {value.GetType()} and of value {value}."); } GenericMethodExample("ABC"); ``` ![](imgs/13%20Generics%2C%20IEnumerable%20and%20LINQ_2.png) * ***Note:*** You could name the generic type as anything, e.g. ``. It is named `` by convention. ## Exercise 1: Initializing a populated list Create a generic method `GetPopulatedList` 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 list = GetPopulatedList("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 names = new string[] {"Harry", "Luke", "Harley"}; IEnumerable days = new List {"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 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 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...
...a method: ```csharp bool GetUserWithId(User user) { return user.Id == 1; } Console.WriteLine( userList.First( GetUserWithId).Name); // Outputs "Ville" ```
...an anonymous method: ```csharp Console.WriteLine(userList.First( delegate (User user) { return user.Id == 1; })); // Outputs "Ville" ```
...a lambda expression: ```csharp Console.WriteLine(userList.First( user => user.Id == 1).Name); // Outputs "Ville" ```
### 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 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 persons = new List {/* 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 {/* 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
```csharp var methodResult = persons .Where(person => person.Country == "Finland"); ```
```csharp var queryResult = from person in persons where person.Country == "Finland" select person; ```
* 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
```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)
```csharp string[] strings = new string[] { "Timo", "Pekka", "Taina", "Kalle" }; string[] queryResult = strings .Where(s => s.StartsWith('T')) .ToArray(); ``` No error!
## Exercise 2: Filtering Names * 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 * 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 * 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)!