From cc779adf082f3d6e1254862ddecad8fa5aa96406 Mon Sep 17 00:00:00 2001 From: borb Date: Mon, 23 Jun 2025 15:01:25 +0300 Subject: [PATCH] finish lecture 13, add css --- .themes/buutti.css | 15 + ...Generics, IEnumerable and LINQ-slides.html | 530 +++++++++++++ 13. Generics, IEnumerable and LINQ.md | 738 ++++++++---------- 3 files changed, 880 insertions(+), 403 deletions(-) create mode 100644 13. Generics, IEnumerable and LINQ-slides.html diff --git a/.themes/buutti.css b/.themes/buutti.css index 3a89ab0..0a0442f 100644 --- a/.themes/buutti.css +++ b/.themes/buutti.css @@ -18,6 +18,21 @@ grid-template-columns: 2fr 1fr; gap: 1rem; } +.columns32 { + display: grid; + grid-template-columns: 3fr 2fr; + gap: 1rem; +} +.columns23 { + display: grid; + grid-template-columns: 2fr 3fr; + gap: 1rem; +} +.columns111 { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 1rem; +} .centered { display: flex; flex-direction: column; diff --git a/13. Generics, IEnumerable and LINQ-slides.html b/13. Generics, IEnumerable and LINQ-slides.html new file mode 100644 index 0000000..6aab362 --- /dev/null +++ b/13. Generics, IEnumerable and LINQ-slides.html @@ -0,0 +1,530 @@ +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:
    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

+
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();
+  }
+}
+
+

+
+
+

Multiple type parameters

+

Generic classes can receive multiple types as parameters:

+
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

+

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

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

Exercise 1: Initializing a populated list

+ +

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:

+
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
    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:
    +
  • +
  • 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

    +
    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 namespace to your project:
    using System.Linq;
    +
    +
  • +
+
+
+
    +
  • +

    Here's a solution to the earlier example that uses LINQ:

    +
    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 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 as a parameter
  • +
  • We could just declare a method beforehand, and then refer to it in First like this:
    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:
    Console.WriteLine(userList.First(delegate (User user)
    +  { return user.Id == 1; }));    // Outputs "Ville"
    +
    +
  • +
+
+
+

Lambda expressions compared

+

Using LINQs "First" -query with...

+
+
+

...a method:

+
bool GetUserWithId(User user)
+{
+  return user.Id == 1;
+}
+Console.WriteLine(
+  userList.First(
+    GetUserWithId).Name);
+// Outputs "Ville"
+
+
+
+

...an anonymous method:

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

...a lambda expression:

+
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

+
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...
    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:
    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:
    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":
    List<Person> persons = new List<Person> {/* Insert data here */};
    +
    +var queryResult = persons
    +  .Where(person => person.Country == "Finland");
    +
    +
  • +
+
+
+

LINQ methods listed

+ + + + + + + + + + + + + + + + + + + + + + + + + +
MethodExampleDescription
Wherepersons.Where(p => p.Country == "Finland")Filters results based on an expression
OrderBypersons.OrderBy(p => p.LastName)Orders results based on one of its properties
Selectpersons.Select(p => $"Dr. {p.LastName}")Converts the enumerable into an another type based on an expression
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodExampleDescription
Skippersons.Skip(1)Skips first N elements
Takepersons.Take(5)Returns N elements
ToListpersons.ToList()Converts IEnumerable to a list
ToArraypersons.ToArray()Converts IEnumerable to an array
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodExampleDescription
Anypersons.Any(p => p.Country == "Finland")Return true if at least one element satisfies a condition
Allpersons.All(p => p.Age >= 18)Return true if all elements satisfy a condition
FirstOrDefaultpersons.FirstOrDefault(p => !p.Active)Returns the first element that satisfies a condition, or null if not found
Countpersons.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

+
+
+

LINQ: A chained example

+
    +
  • Suppose we have a csv file authors.csv in our project directory.
    // 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:
    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
    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
    • +
    +
  • +
+
+
+
var methodResult = persons
+  .Where(person => 
+    person.Country == "Finland");
+
+
+
+
var queryResult = from person in persons
+  where person.Country == "Finland"
+  select person;
+
+
+
+ +
+
+

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
  • +
+
+
+
string[] strings = new string[] 
+  { "Timo", "Pekka", "Taina", "Kalle" };
+string[] queryResult = strings
+  .Where(s => s.StartsWith('T'));
+
+

+
+
+
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
  • +
  • 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!
  • +
+
+
\ No newline at end of file diff --git a/13. Generics, IEnumerable and LINQ.md b/13. Generics, IEnumerable and LINQ.md index 8964672..13c00fb 100644 --- a/13. Generics, IEnumerable and LINQ.md +++ b/13. Generics, IEnumerable and LINQ.md @@ -1,528 +1,460 @@ -# Generics, IEnumerable and LINQ - -![](imgs/13%20Generics%2C%20IEnumerable%20and%20LINQ_0.png) - +--- +marp: true +paginate: true +math: mathjax +theme: buutti +title: N. 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: - -List numberList = new List(); // This list stores variables of type int +# Generics, IEnumerable and LINQ -This means that list is a __generic__ class: it can contain data of any type + + -Classes, structs, interfaces and methods can also be generic +## Generics -All the variables that are defined generic inside a generic container will be assigned a type only when the containing object/method is called +* 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 -# Generics - Creating a Generic Class +### 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}"); - -} - + 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(); - + static void Main(string[] args) + { + GenericClassExample example = new GenericClassExample(); + example.value = 20; + example.PrintTypeAndValue(); + } } - -} - +``` ![](imgs/13%20Generics%2C%20IEnumerable%20and%20LINQ_1.png) -# Generics - Multiple Type Parameters +### Multiple type parameters -Generic classes can receive multiple types as 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; } - + 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; - + static void Main(string[] args) + { + CustomContainer container + = new CustomContainer(); + container.First = 10; + container.Second = "Testing."; + container.Third = DateTime.Now; + } } +``` -} +### Creating a generic Method -# Generics - 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}."); - + 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) -❕ - -You could name the generic type as anything, e.g. \. It is named \ by convention. +* ***Note:*** You could name the generic type as anything, e.g. ``. It is named `` by convention. -# Exercise 1: Initializing a Populated List +## 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. +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 - -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); - -# IEnumerable (continued) - -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 - -Some common query expressions occur repeatedly in code - -E.g. trying to find an object with a certain id from an array: - -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(value); } +``` -Console.WriteLine(FindObjectWithId(1).Name); // Outputs "Ville" +## IEnumerable -# LINQ (continued) +* 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"}; -* Having to write your own method for each possible query operation (select, filter, sort…) would of course 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 -* Add to this all the different types of data (objects, SQL databases, XML, JSON…) + foreach (string name in names) + Console.WriteLine(name); + + foreach (string day in days) + Console.WriteLine(day); + ``` -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 namespace to your project: - -using System.Linq; - -Here's the first example using LINQ: - -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 __First __ -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 __ as a parameter +--- -The following code would do exactly the same thing as the example in the previous slide: +* 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 -// Parameter references an existing method +## LINQ -bool GetUserWithId(User user) +### 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 -return user.Id == 1; + ```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" + ``` -Console.WriteLine(userList.First(GetUserWithId).Name); // Outputs "Ville" +--- -As would the following: +* 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 delegate is created inside the parameters as an anonymous method +### The solution -Console.WriteLine(userList.First(delegate (User user) +* 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; + ``` -{ return user.Id == 1; })); // Outputs "Ville" +--- -# Lambda Expressions (continued) +* 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; - + return user.Id == 1; } - Console.WriteLine( - -userList.First( - -GetUserWithId).Name); - + userList.First( + GetUserWithId).Name); // Outputs "Ville" +``` + +
+
...an anonymous method: +```csharp Console.WriteLine(userList.First( - delegate (User user) - { - -return user.Id == 1; - + return user.Id == 1; })); - // Outputs "Ville" +``` + +
+
...a lambda expression: +```csharp Console.WriteLine(userList.First( - -user => - -user.Id == 1).Name); - + user => + user.Id == 1).Name); // Outputs "Ville" +``` -# Lambda Expressions - Example - -Normal methods can also be declared using the arrow function - -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! +### Lambda expressions: An example -} - -# LINQ and Lambda Expressions - -Going back to our LINQ example, it could be written without lambda expressions: - -bool GetUserWithId(User user) +Regular methods can also be declared using the arrow function +```csharp +static void Main(string[] args) { - -return user.Id == 1; - + // 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! } - -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(GetUserWithId).Name); // Outputs "Ville" - -Here's the shorter version with lambda expression again: - -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 persons object, returning only the persons whose country is of value "Finland": - -List persons = new List {/* Insert data here */}; - -var queryResult = persons - -.Where(person => person.Country == "Finland"); - -# LINQ Methods (continued) - -| 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 | -| 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 | +``` + +### 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 | --- -Tässä on kaikki aritmeettiset operaattorit - -| 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) +| 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 | --- -Tässä on kaikki aritmeettiset operaattorit - -# LINQ - Example - -Suppose we have a csv file authors.csv in our project directory - -// 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 is now an in-memory list containing all the authors from the original csv file. Note how methods can be chained! - -# Query Syntax - -An alternative way of using LINQ is with the __query syntax__ - -The following performs filtering to persons object: - -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 - 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 +| 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 | -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); +More LINQ methods can be found [here](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable?view=net-7.0) -# Extension Methods vs Query Syntax +### 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 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"); +``` -.Where(person => person.Country == "Finland"); +
+
+```csharp var queryResult = from person in persons + where person.Country == "Finland" + select person; +``` -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) -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 -# 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 -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: - -string[] strings = new string[] { "Timo", "Pekka", "Taina", "Kalle" }; - -string[] queryResult = strings - -.Where(s => s.StartsWith('T')); +
+
+ ```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) -string[] strings = new string[] { "Timo", "Pekka", "Taina", "Kalle" }; +
+
+```csharp +string[] strings = new string[] + { "Timo", "Pekka", "Taina", "Kalle" }; string[] queryResult = strings + .Where(s => s.StartsWith('T')) + .ToArray(); +``` -.Where(s => s.StartsWith('T')) - -.ToArray(); // This works - -# 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. +No error! -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 +## Exercise 2: Filtering Names + -Recap: IEnumerable itself only contains one method +* 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! -How does the LINQ library suddenly add all these methods to our Enumerables? +## Exercise 3: Queries on Object Lists + -This is possible with extension methods: +* 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 -[https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods) +## 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)! \ No newline at end of file