You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
csharp-basics/13. Generics, IEnumerable a...

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:

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods