Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
C# 7 and .NET Core Cookbook

You're reading from   C# 7 and .NET Core Cookbook Serverless programming, Microservices and more

Arrow left icon
Product type Paperback
Published in Apr 2017
Publisher
ISBN-13 9781787286276
Length 628 pages
Edition 2nd Edition
Languages
Tools
Arrow right icon
Author (1):
Arrow left icon
Dirk Strauss Dirk Strauss
Author Profile Icon Dirk Strauss
Dirk Strauss
Arrow right icon
View More author details
Toc

Table of Contents (17) Chapters Close

Preface 1. New Features in C# 7.0 2. Classes and Generics FREE CHAPTER 3. Object-Oriented Programming in C# 4. Code Analyzers in Visual Studio 5. Regular Expressions 6. Working with Files, Streams, and Serialization 7. Making Apps Responsive with Asynchronous Programming 8. High Performance Programming Using Parallel and Multithreading in C# 9. Composing Event-Based Programs Using Reactive Extensions 10. Exploring .NET Core 1.1 11. ASP.NET Core on the MVC Framework 12. Choosing and Using a Source Control Strategy 13. Creating a Mobile Application in Visual Studio 14. Writing Secure Code and Debugging in Visual Studio 15. Creating Microservices on Azure Service Fabric 16. Azure and Serverless Computing

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.

lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image