Book Image

C# 7 and .NET Core Cookbook - Second Edition

Book Image

C# 7 and .NET Core Cookbook - Second Edition

Overview of this book

C# has recently been open-sourced and C# 7 comes with a host of new features for building powerful, cross-platform applications. This book will be your solution to some common programming problems that you come across with C# and will also help you get started with .NET Core 1.1. Through a recipe-based approach, this book will help you overcome common programming challenges and get your applications ready to face the modern world. We start by running you through new features in C# 7, such as tuples, pattern matching, and so on, giving you hands-on experience with them. Moving forward, you will work with generics and the OOP features in C#. You will then move on to more advanced topics, such as reactive extensions, Regex, code analyzers, and asynchronous programming. This book will also cover new, cross-platform .NET Core 1.1 features and teach you how to utilize .NET Core on macOS. Then, we will explore microservices as well as serverless computing and how these benefit modern developers. Finally, you will learn what you can do with Visual Studio 2017 to put mobile application development across multiple platforms within the reach of any developer.
Table of Contents (17 chapters)

Pattern matching

C# 7.0 introduces an aspect common to functional programming languages with pattern matching. This new kind of construct can test values in different ways. To accomplish this, two language constructs in C# 7.0 have been enhanced to take advantage of patterns. These are as follows:

  • The is expression
  • The case clause in switch statements

With regard to the is expression, developers can now have a pattern on the right instead of just a type. When it comes to switch statements, the case clause can now match on patterns. The switch statement is no longer limited to primitive types and can switch on anything. Let's start by looking at the is expression.

Getting ready

To illustrate the concept of pattern matching, assume the following scenario. We have two object types called Student and Professor. We want to minimize code, so we want to create a single method to output the data from the object passed to it. This object can be a Student or a Professor object. The method needs to figure out which object it is working with and act accordingly. But first, we need to do a few things inside our console application to set things up:

  1. Ensure that you have added the following using statement.
        using System.Collections.Generic;
  1. You now need to create two new classes called Student and Professor. The code for the Student class needs to look as follows:
        public class Student
{
public string Name { get; set; }
public string LastName { get; set; }
public List<int> CourseCodes { get; set; }
}
  1. Next, the code for the Professor class needs to look as follows:
        public class Professor
{
public string Name { get; set; }
public string LastName { get; set; }
public List<string> TeachesSubjects { get; set; }
}

To understand where we are going with pattern matching, we first need to understand where we have come from. I will start the next section off by showing you how developers might have written this code before C# 7.0.

How to do it...

  1. In the Chapter1 class, create a new method called OutputInformation() that takes a person object as parameter.
        public void OutputInformation(object person)
{

}
  1. Inside this method, we would need to check what type of object is passed to it. Traditionally, we would need to do the following:
        if (person is Student)
{
Student student = (Student)person;
WriteLine($"Student {student.Name} {student.LastName}
is enrolled for courses {String.Join<int>(
", ", student.CourseCodes)}");
}

if (person is Professor)
{
Professor prof = (Professor)person;
WriteLine($"Professor {prof.Name} {prof.LastName}
teaches {String.Join<string>(",", prof.TeachesSubjects)}");
}
  1. We have two if statements. We are expecting either a Student object or a Professor object. The complete OutputInformation() method should look as follows:
        public void OutputInformation(object person)
{
if (person is Student)
{
Student student = (Student)person;
WriteLine($"Student {student.Name} {student.LastName}
is enrolled for courses {String.Join<int>
(", ", student.CourseCodes)}");
}
if (person is Professor)
{
Professor prof = (Professor)person;
WriteLine($"Professor {prof.Name} {prof.LastName}
teaches {String.Join<string>
(",", prof.TeachesSubjects)}");
}
}
  1. Calling this method from the static void Main is easy enough. The objects are similar, but differ in the list they contain. A Student object exposes a list of course codes, while a Professor exposes a list of subjects taught to students.
        static void Main(string[] args)
{
Chapter1 ch1 = new Chapter1();

Student student = new Student();
student.Name = "Dirk";
student.LastName = "Strauss";
student.CourseCodes = new List<int> { 203, 202, 101 };

ch1.OutputInformation(student);

Professor prof = new Professor();
prof.Name = "Reinhardt";
prof.LastName = "Botha";
prof.TeachesSubjects = new List<string> {
"Mobile Development", "Cryptography" };

ch1.OutputInformation(prof);
}
  1. Run the console application and see the OutputInformation() method in action.
  1. While the information we see in the console application is what we expect, we can simplify the code in the OutputInformation() method much more with pattern matching. To do this, modify the code as follows:
        if (person is Student student)
{

}
if (person is Professor prof)
{

}
  1. The first if expression checks to see if the object person is of type Student. If so, it stores that value in the student variable. The same logic is true for the second if expression. If true, the value of person is stored inside the variable prof. For code execution to reach the code between the curly braces of each if expression, the condition had to evaluate to true. We can, therefore, dispense with the cast of the person object to a Student or Professor type, and just use the student or prof variable directly, like so:
        if (person is Student student)
{
WriteLine($"Student {student.Name} {student.LastName}
is enrolled for courses {String.Join<int>
(", ", student.CourseCodes)}");
}
if (person is Professor prof)
{
WriteLine($"Professor {prof.Name} {prof.LastName}
teaches {String.Join<string>
(",", prof.TeachesSubjects)}");
}
  1. Running the console application again, you will see that the output is exactly the same as before. We have, however, written better code that uses type pattern matching to determine the correct output to display.
  1. Patterns, however, don't stop there. You can also use them in constant patterns, which are the simplest type of pattern to use. Let's take a look at the check for the constant null. With pattern matching we can enhance our OutputInformation() method as follows:
        public void OutputInformation(object person)
{
if (person is null)
{
WriteLine($"Object {nameof(person)} is null");
}
}
  1. Change the code that is calling the OutputInformation() method and set it to null.
        Student student = null;
  1. Run your console application and see the message displayed.
It is good practice to use the nameof keyword here. If the variable name person ever has to change, the corresponding output will be changed also.
  1. Lastly, switch statements in C# 7.0 have been improved to make use of pattern matching. C# 7.0 allows us to switch on anything, not just primitive types and strings. The case clauses now make use of patterns, which is really exciting. Let's have a look at how to implement this in the following code examples. We will keep using the Student and Professor types to illustrate the concept of pattern matching in switch statements. Modify the OutputInformation() method and include the boilerplate switch statement as follows. The switch statement still has defaults, but it can now do so much more.
        public void OutputInformation(object person)
{
switch (person)
{
default:
WriteLine("Unknown object detected");
break;
}
}
  1. We can expand the case statement to check for the Professor type. If it matches an object to the Professor type, it can act on that object and use it as a Professor type in the body of the case statement. This means we can call the Professor-specific TeachesSubjects property. We do it like this:
        switch (person)
{
case Professor prof:
WriteLine($"Professor {prof.Name} {prof.LastName}
teaches {String.Join<string>
(",", prof.TeachesSubjects)}");
break;
default:
WriteLine("Unknown object detected");
break;
}
  1. We can also do the same for Student types. Change the code of the switch as follows:
        switch (person)
{
case Student student:
WriteLine($"Student {student.Name} {student.LastName}
is enrolled for courses {String.Join<int>
(", ", student.CourseCodes)}");
break;
case Professor prof:
WriteLine($"Professor {prof.Name} {prof.LastName}
teaches {String.Join<string>
(",", prof.TeachesSubjects)}");
break;
default:
WriteLine("Unknown object detected");
break;
}
  1. One final (and great) feature of case statements remains to be illustrated. We can also implement a when condition, similar to what we saw in C# 6.0 with exception filters. The when condition simply evaluates to a Boolean and further filters the input that it triggers on. To see this in action, change the switch accordingly:
        switch (person)
{
case Student student when (student.CourseCodes.Contains(203)):
WriteLine($"Student {student.Name} {student.LastName}
is enrolled for course 203.");
break;
case Student student:
WriteLine($"Student {student.Name} {student.LastName}
is enrolled for courses {String.Join<int>
(", ", student.CourseCodes)}");
break;
case Professor prof:
WriteLine($"Professor {prof.Name} {prof.LastName}
teaches {String.Join<string>(",",
prof.TeachesSubjects)}");
break;
default:
WriteLine("Unknown object detected");
break;
}
  1. Lastly, to come full circle and check for null values, we can modify our switch statement to cater for those too. The completed switch statement is, therefore, as follows:
        switch (person)
{
case Student student when (student.CourseCodes.Contains(203)):
WriteLine($"Student {student.Name} {student.LastName}
is enrolled for course 203.");
break;
case Student student:
WriteLine($"Student {student.Name} {student.LastName}
is enrolled for courses {String.Join<int>
(", ", student.CourseCodes)}");
break;
case Professor prof:
WriteLine($"Professor {prof.Name} {prof.LastName}
teaches {String.Join<string>
(",", prof.TeachesSubjects)}");
break;
case null:
WriteLine($"Object {nameof(person)} is null");
break;
default:
WriteLine("Unknown object detected");
break;
}
  1. Running the console application again, you will see that the first case statement containing the when condition is triggered for the Student type.

How it works...

With pattern matching, we saw that patterns are used to test whether a value is of a certain type.

You will also hear some developers say that they test whether the value has a certain shape.

When we find a match we can get to the information specific to that type (or shape). We saw this in the code where we accessed the CourseCodes property, which was specific to the Student type and the TeachesSubjects property, which was specific to the Professor type.

Lastly, you now need to pay careful attention to the order of your case statements, which now matters. The case statement that uses the when clause is more specific than the statement that simply checks for a Student type. This means that the when case needs to happen before the Student case because both of these cases are of type Student. If the Student case happens before the when clause, it will never trigger the switch for Students that have course code 203.

Another important thing to remember is that the default clause will always be evaluated last, irrespective of where it appears in the switch statement. It is, therefore, good practice to write it as the last clause in a switch statement.