Nota
Capaian ke halaman ini memerlukan kebenaran. Anda boleh cuba mendaftar masuk atau menukar direktori.
Capaian ke halaman ini memerlukan kebenaran. Anda boleh cuba menukar direktori.
This tutorial teaches you how to use pattern matching to inspect data in C#. You write small amounts of code, then you compile and run that code. The tutorial contains a series of lessons that explore different kinds of patterns supported by C#. These lessons teach you the fundamentals of the C# language.
In this tutorial, you:
- Launch a GitHub Codespace with a C# development environment.
- Test data for discrete values.
- Match enum data against value.
- Create exhaustive matches using
switchexpressions. - Match types using type patterns.
Prerequisites
You must have one of the following:
- A GitHub account to use GitHub Codespaces. If you don't already have one, you can create a free account at GitHub.com.
- A computer with the following tools installed:
- The .NET 10 SDK.
- Visual Studio Code.
- The C# DevKit.
Match a value
The preceding tutorials demonstrated built-in types and types you define as tuples or records. You can check instances of these types against a pattern. Whether an instance matches a pattern determines the actions your program takes. In the examples below, you see ? after type names. This symbol allows the value of this type to be null (for example, bool? can be true, false, or null). For more information, see Nullable value types. Let's start to explore how you can use patterns.
Open a browser window to GitHub codespaces. Create a new codespace from the .NET Template. If you completed other tutorials in this series, you can open that codespace.
When your codespace loads, create a new file in the tutorials folder named patterns.cs.
Open your new file.
All the examples in this tutorial use text input that represents a series of bank transactions as comma separated values (CSV) input. In each of the samples, you can match the record against a pattern by using either an
isorswitchexpression. This first example splits each line on the,character and then matches the first string field against the value "DEPOSIT" or "WITHDRAWAL" by using anisexpression. When it matches, the transaction amount is added or deducted from the current account balance. To see it work, add the following code to patterns.cs:string bankRecords = """ DEPOSIT, 10000, Initial balance DEPOSIT, 500, regular deposit WITHDRAWAL, 1000, rent DEPOSIT, 2000, freelance payment WITHDRAWAL, 300, groceries DEPOSIT, 700, gift from friend WITHDRAWAL, 150, utility bill DEPOSIT, 1200, tax refund WITHDRAWAL, 500, car maintenance DEPOSIT, 400, cashback reward WITHDRAWAL, 250, dining out DEPOSIT, 3000, bonus payment WITHDRAWAL, 800, loan repayment DEPOSIT, 600, stock dividends WITHDRAWAL, 100, subscription fee DEPOSIT, 1500, side hustle income WITHDRAWAL, 200, fuel expenses DEPOSIT, 900, refund from store WITHDRAWAL, 350, shopping DEPOSIT, 2500, project milestone payment WITHDRAWAL, 400, entertainment """; double currentBalance = 0.0; var reader = new StringReader(bankRecords); string? line; while ((line = reader.ReadLine()) is not null) { if (string.IsNullOrWhiteSpace(line)) continue; // Split the line based on comma delimiter and trim each part string[] parts = line.Split(','); string? transactionType = parts[0]?.Trim(); if (double.TryParse(parts[1].Trim(), out double amount)) { // Update the balance based on transaction type if (transactionType?.ToUpper() is "DEPOSIT") currentBalance += amount; else if (transactionType?.ToUpper() is "WITHDRAWAL") currentBalance -= amount; Console.WriteLine($"{line.Trim()} => Parsed Amount: {amount}, New Balance: {currentBalance}"); } }Then, type the following text in the terminal window:
cd tutorials dotnet patterns.csExamine the output. You can see that each line is processed by comparing the value of the text in the first field.
You could similarly construct the preceding sample by using the == operator to test that two string values are equal. Comparing a variable to a constant is a basic building block for pattern matching. Let's explore more of the building blocks that are part of pattern matching.
Enum matches
Another common use for pattern matching is matching on the values of an enum type. The following sample processes the input records to create a tuple where the first value is an enum value that notes a deposit or a withdrawal. The second value is the value of the transaction.
Add the following code to the end of your source file. It defines the
TransactionTypeenumeration:public enum TransactionType { Deposit, Withdrawal, Invalid }Add a function to parse a bank transaction into a tuple that holds the transaction type and the value of the transaction. Add the following code before your declaration of the
TransactionTypeenum:static IEnumerable<(TransactionType type, double amount)> TransactionRecords(string inputText) { var reader = new StringReader(inputText); string? line; while ((line = reader.ReadLine()) is not null) { string[] parts = line.Split(','); string? transactionType = parts[0]?.Trim(); if (double.TryParse(parts[1].Trim(), out double amount)) { // Update the balance based on transaction type if (transactionType?.ToUpper() is "DEPOSIT") yield return (TransactionType.Deposit, amount); else if (transactionType?.ToUpper() is "WITHDRAWAL") yield return (TransactionType.Withdrawal, amount); } else { yield return (TransactionType.Invalid, 0.0); } } }Add a new loop to process the transaction data by using the
TransactionTypeenumeration you declared:currentBalance = 0.0; foreach (var transaction in TransactionRecords(bankRecords)) { if (transaction.type == TransactionType.Deposit) currentBalance += transaction.amount; else if (transaction.type == TransactionType.Withdrawal) currentBalance -= transaction.amount; Console.WriteLine($"{transaction.type} => Parsed Amount: {transaction.amount}, New Balance: {currentBalance}"); }
The preceding example also uses an if statement to check the value of an enum expression. Another form of pattern matching uses a switch expression. Let's explore that syntax and how you can use it.
Exhaustive matches with switch
A series of if statements can test a series of conditions. But, the compiler can't tell if a series of if statements are exhaustive or if later if conditions are subsumed by earlier conditions. Exhaustive means that one of the if or else clauses in the series of tests handles all possible inputs. If a series of if statements are exhaustive, every possible input satisfies at least one if or else clause. Subsumption means that a later if or else clause can't be reached because earlier if or else clauses match all possible inputs. For example, in the following example code, one clause never matches:
int n = GetNumber();
if (n < 20)
Console.WriteLine("n is less than 20");
else if (n < 10)
Console.WriteLine("n is less than 10"); // unreachable
else
Console.WriteLine("n is greater than 20");
The else if clause never matches because every number less than 10 is also less than 20. The switch expression ensures both of those characteristics are met, which results in fewer bugs in your apps. Let's try it and experiment.
Copy the following code. Replace the two
ifstatements in yourforeachloop with theswitchexpression you copied.currentBalance += transaction switch { (TransactionType.Deposit, var amount) => amount, (TransactionType.Withdrawal, var amount) => -amount, _ => 0.0, };Type
dotnet patterns.csin the terminal window to run the new sample.When you run the code, you see that it works the same.
To demonstrate subsumption, reorder the switch arms as shown in the following snippet:
currentBalance += transaction switch { (TransactionType.Deposit, var amount) => amount, _ => 0.0, (TransactionType.Withdrawal, var amount) => -amount, };After you reorder the switch arms, type
dotnet patterns.csin the terminal window. The compiler issues an error because the arm with_matches every value. As a result, that final arm withTransactionType.Withdrawalnever runs. The compiler tells you that something's wrong in your code.The compiler issues a warning if the expression tested in a
switchexpression could contain values that don't match any switch arm. If some values could fail to match any condition, theswitchexpression isn't exhaustive. The compiler also issues a warning if some values of the input don't match any of the switch arms.Remove the line with
_ => 0.0,, so that any invalid values don't match.Type
dotnet patterns.csto see the results.The compiler issues a warning. The test data is valid, so the program works. However, any invalid data would cause a failure at runtime.
Type patterns
To finish this tutorial, explore one more building block for pattern matching: the type pattern. A type pattern tests an expression at run time to see if it's the specified type. You can use a type test with either an is expression or a switch expression. Modify the current sample in two ways. First, instead of a tuple, build Deposit and Withdrawal record types that represent the transactions.
Add the following declarations at the end of your code file:
public record Deposit(double Amount, string description); public record Withdrawal(double Amount, string description);Add this method just before the declaration of the
TransactionTypeenumeration. It parses the text and returns a series of records:static IEnumerable<object?> TransactionRecordType(string inputText) { var reader = new StringReader(inputText); string? line; while ((line = reader.ReadLine()) is not null) { string[] parts = line.Split(','); string? transactionType = parts[0]?.Trim(); if (double.TryParse(parts[1].Trim(), out double amount)) { // Update the balance based on transaction type if (transactionType?.ToUpper() is "DEPOSIT") yield return new Deposit(amount, parts[2]); else if (transactionType?.ToUpper() is "WITHDRAWAL") yield return new Withdrawal(amount, parts[2]); } yield return default; } }Add the following code after the last
foreachloop:currentBalance = 0.0; foreach (var transaction in TransactionRecordType(bankRecords)) { currentBalance += transaction switch { Deposit d => d.Amount, Withdrawal w => -w.Amount, _ => 0.0, }; Console.WriteLine($" {transaction} => New Balance: {currentBalance}"); }Type
dotnet patterns.csin the terminal window to see the results. This final version tests the input against a type.
Pattern matching provides a vocabulary to compare an expression against characteristics. Patterns can include the expression's type, values of types, property values, and combinations of them. Comparing expressions against a pattern can be clearer than multiple if comparisons. You explored some of the patterns you can use to match expressions. There are many more ways to use pattern matching in your applications. As you explore, you can learn more about pattern matching in C# in the following articles:
Cleanup resources
GitHub automatically deletes your Codespace after 30 days of inactivity. You completed all the tutorials in this series. To delete your Codespace now, open a browser window and go to your Codespaces. You should see a list of your codespaces in the window. Select the three dots (...) in the entry for the learn tutorial codespace and select delete.
Related content
- Download and install the .NET 10 SDK.
- Download and install Visual Studio Code.
- Download and install the C# DevKit.
- Explore the C# fundamentals section to learn more about C#.