DEV Community

mohamed Tayel
mohamed Tayel

Posted on

c# advanced: Pattern Matching

Meta Description:
Learn the evolution of pattern matching in C# with clear explanations and detailed code examples. Explore type, declaration, constant, relational, logical, property, and more pattern types for writing cleaner, more maintainable code.

Introduction

Welcome to our in-depth guide on pattern matching in C#! Pattern matching is a feature that helps you write clearer and more concise code by checking the structure and type of objects. Originally introduced in C# 7, it has evolved to include many powerful patterns that make code easier to manage.

In this article, we'll walk you through different pattern types in C#, each accompanied by clear explanations and full example code.

What is Pattern Matching?

Pattern matching is like asking questions about an object:

  • What type is it?
  • What properties does it have?
  • Does it meet certain conditions?

Before pattern matching, you might have written complex if-else blocks or switch statements to handle these checks. With pattern matching, the same logic can be expressed in a cleaner, more understandable way.

Evolution of Pattern Matching in C

Pattern matching has improved over the years, adding more types and patterns with each C# version. The major types of patterns include:

  1. Type Patterns
  2. Declaration Patterns
  3. Constant Patterns
  4. Relational Patterns
  5. Logical Patterns
  6. Property Patterns
  7. Positional Patterns
  8. Var Patterns
  9. Discard Patterns
  10. Parenthesized Patterns

Let’s explore each pattern type with code examples!

1. Type Patterns

Type patterns allow you to check the type of an object and assign it to a local variable.

Example

object obj = "Hello, World!";

if (obj is string message)
{
    Console.WriteLine($"Message length: {message.Length}");
}
Enter fullscreen mode Exit fullscreen mode

In this code:

  • obj is checked to see if it's of type string.
  • If true, obj is assigned to the variable message, allowing you to access the Length property directly.

2. Declaration Patterns

Declaration patterns combine type checking with variable declaration.

Example

object data = 100;

if (data is int number)
{
    Console.WriteLine($"Number is: {number}");
}
Enter fullscreen mode Exit fullscreen mode

In this example:

  • If data is an integer, it is assigned to number, making it easy to use in the conditional block.

3. Constant Patterns

Constant patterns check whether a value matches a constant.

Example

int orderStatus = 1;

switch (orderStatus)
{
    case 0:
        Console.WriteLine("Order is pending.");
        break;
    case 1:
        Console.WriteLine("Order is confirmed.");
        break;
    case 2:
        Console.WriteLine("Order is shipped.");
        break;
    default:
        Console.WriteLine("Unknown status.");
        break;
}
Enter fullscreen mode Exit fullscreen mode

Here:

  • The switch statement checks if orderStatus matches any of the constants (0, 1, 2).

4. Relational Patterns

Relational patterns allow you to compare values using relational operators (<, >, <=, >=).

Example

int amount = 150;

if (amount is > 100)
{
    Console.WriteLine("High-value order.");
}
Enter fullscreen mode Exit fullscreen mode

In this code:

  • It checks if amount is greater than 100 using the relational pattern.

5. Logical Patterns

Logical patterns combine multiple patterns using logical operators like and (&&), or (||), and not (!).

Example

int age = 25;

if (age is > 18 && age is < 60)
{
    Console.WriteLine("Eligible for the program.");
}
Enter fullscreen mode Exit fullscreen mode

Here:

  • The code checks if age is between 18 and 60 using the and operator.

6. Property Patterns

Property patterns check specific properties of an object.

Example

var order = new { Status = "Shipped", Amount = 200 };

if (order is { Status: "Shipped", Amount: > 150 })
{
    Console.WriteLine("High-value shipped order.");
}
Enter fullscreen mode Exit fullscreen mode

In this code:

  • The property pattern checks if Status is "Shipped" and Amount is greater than 150.

7. Positional Patterns

Positional patterns deconstruct objects and match based on the position of their components.

Example

var point = (3, 4);

if (point is (3, 4))
{
    Console.WriteLine("Point is at (3, 4).");
}
Enter fullscreen mode Exit fullscreen mode

Here:

  • The positional pattern checks if the tuple point matches the values 3 and 4.

8. Var Patterns

Var patterns match any value and assign it to a variable, making them useful when you want to handle an object without knowing its type.

Example

object obj = 123;

if (obj is var value)
{
    Console.WriteLine($"Matched value: {value}");
}
Enter fullscreen mode Exit fullscreen mode

In this example:

  • The var pattern matches obj and assigns it to value, regardless of its type.

9. Discard Patterns

The discard pattern ignores values that are not needed, often used to skip certain fields in deconstruction.

Example

var person = ("John", 30);

if (person is (_, 30))
{
    Console.WriteLine("Person is 30 years old.");
}
Enter fullscreen mode Exit fullscreen mode

In this code:

  • The discard pattern ignores the first value and only checks if the second value is 30.

10. Parenthesized Patterns

Parenthesized patterns allow you to group patterns for clarity.

Example

int score = 75;

if (score is (> 50 and < 100))
{
    Console.WriteLine("Score is between 50 and 100.");
}
Enter fullscreen mode Exit fullscreen mode

Here:

  • The parenthesized pattern groups the conditions, making it clear that both checks are part of the same logic.

Complete Example: Combining Multiple Patterns

Let's create a more complex example using multiple pattern types together:

public class Order
{
    public IShippingProvider Provider { get; set; }
    public string Status { get; set; }
    public int Amount { get; set; }
}

public class SwedishPostalServiceShippingProvider : IShippingProvider
{
    public int FreightCost { get; set; }
}

var order = new Order
{
    Provider = new SwedishPostalServiceShippingProvider { FreightCost = 120 },
    Status = "Shipped",
    Amount = 300
};

if (order is 
    { 
        Provider: SwedishPostalServiceShippingProvider { FreightCost: > 100 } swedishProvider,
        Status: "Shipped",
        Amount: > 250 
    })
{
    Console.WriteLine("High-value, shipped order using Swedish provider.");
}
Enter fullscreen mode Exit fullscreen mode

In this complete example:

  • The property pattern checks properties within order.
  • The type pattern checks if Provider is of type SwedishPostalServiceShippingProvider.
  • The relational pattern checks if FreightCost and Amount meet the specified conditions.

Summary

Pattern matching in C# has come a long way since its introduction in C# 7. It helps you write more readable and maintainable code by allowing for clear type checks, property evaluations, and complex logic combinations. With different patterns available, you can handle various scenarios more efficiently.

Practice Assignments

To strengthen your understanding, try these exercises:

Here's a more detailed breakdown for each assignment:

1. Easy: Write Code That Checks if an Object is a String Using the Type Pattern

Objective

The goal here is to use the type pattern to determine whether an object is of type string. If it is, you’ll print the length of the string.

Solution

The type pattern is a straightforward way to check the type of an object and assign it to a local variable if the type matches. Here's the detailed code:

// Example object
object obj = "Hello, Pattern Matching!";

// Using the type pattern to check if obj is a string
if (obj is string message)
{
    Console.WriteLine($"The object is a string with length: {message.Length}");
}
else
{
    Console.WriteLine("The object is not a string.");
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • The is operator checks if obj is of type string.
  • If true, it assigns the value to the variable message, allowing you to use it within the block.
  • If the check fails, it moves to the else block, indicating that the object is not a string.

2. Medium: Use a Switch Expression to Categorize Orders Based on Their Status

Objective

This assignment involves using a switch expression to classify orders based on their status property. This will help you practice using the compact and modern way of handling pattern matching in C#.

Solution

Here, we’ll create an order object with a Status property and use a switch expression to categorize the order:

// Order class with Status property
public class Order
{
    public string Status { get; set; }
}

// Example order
var order = new Order { Status = "Confirmed" };

// Using a switch expression to categorize the order status
string orderCategory = order.Status switch
{
    "Pending" => "The order is still pending.",
    "Confirmed" => "The order is confirmed.",
    "Shipped" => "The order has been shipped.",
    _ => "Unknown order status."
};

Console.WriteLine(orderCategory);
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • The switch expression takes order.Status as input and matches it against different string values like "Pending", "Confirmed", and "Shipped".
  • For each match, it returns a corresponding message.
  • If none of the patterns match, it returns "Unknown order status." using the default pattern _.

3. Difficult: Write Code That Uses Recursive Patterns to Check the Type and Properties of an Object

Objective

The goal is to use recursive patterns to check both the type of an object and its properties. Recursive patterns allow you to nest different types of patterns within one another.

Solution

Let’s assume we have an Order class with a Provider property that can be of type SwedishPostalServiceShippingProvider, and the Provider has a FreightCost property. We’ll use recursive patterns to check the type and properties.

// Define an interface and classes
public interface IShippingProvider { }

public class SwedishPostalServiceShippingProvider : IShippingProvider
{
    public int FreightCost { get; set; }
}

public class Order
{
    public IShippingProvider Provider { get; set; }
    public string Status { get; set; }
    public int Amount { get; set; }
}

// Example order object
var order = new Order
{
    Provider = new SwedishPostalServiceShippingProvider { FreightCost = 150 },
    Status = "Shipped",
    Amount = 300
};

// Using recursive patterns to check type and properties
if (order is 
    { 
        Provider: SwedishPostalServiceShippingProvider { FreightCost: > 100 } swedishProvider,
        Status: "Shipped",
        Amount: > 250 
    })
{
    Console.WriteLine("High-value, shipped order using Swedish provider.");
}
else
{
    Console.WriteLine("Order does not meet the criteria.");
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • The code first checks if the order's Provider is of type SwedishPostalServiceShippingProvider and if the FreightCost is greater than 100.
  • Then, it checks if the Status is "Shipped" and the Amount is greater than 250.
  • This demonstrates recursive patterns, where different patterns are combined to perform a complex check on nested properties.

Summary of Assignments

  • Easy: You learned to use the type pattern to check and assign a variable when an object is of a specific type.
  • Medium: You used a switch expression to handle different values of a property compactly.
  • Difficult: You explored recursive patterns to perform nested checks on both the type and properties of an object.

Top comments (0)