12 KiB
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
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
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();
}
}
Generics - 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;
}
}
Generics - Creating a Generic Method
void GenericMethodExample(T value)
{
Console.WriteLine
($"This method was passed a variable of type {value.GetType()} and of value {value}.");
}
GenericMethodExample("ABC");
❕
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 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
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(FindObjectWithId(1).Name); // Outputs "Ville"
LINQ (continued)
- 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…)
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:
// Parameter references an existing method
bool GetUserWithId(User user)
{
return user.Id == 1;
}
Console.WriteLine(userList.First(GetUserWithId).Name); // Outputs "Ville"
As would the following:
// The delegate is created inside the parameters as an anonymous method
Console.WriteLine(userList.First(delegate (User user)
{ return user.Id == 1; })); // Outputs "Ville"
Lambda Expressions (continued)
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 - 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!
}
LINQ and Lambda Expressions
Going back to our LINQ example, it could be written without lambda expressions:
bool GetUserWithId(User user)
{
return user.Id == 1;
}
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 |
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
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
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
var methodResult = persons
.Where(person => person.Country == "Finland");
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
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(); // 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
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: