C# pattern matching analogue of Rust match/case with destructuring
Asked Answered
P

1

1

In rust you can do things like this:

for n in 1..101 {
  let triple = (n % 5, n % 2, n % 7);
  match triple {
    // Destructure the second and third elements
    (0, y, z) => println!("First is `0`, `y` is {:?}, and `z` is {:?}", y, z),
    (1, ..)  => println!("First is `1` and the rest doesn't matter"),
    (.., 2)  => println!("last is `2` and the rest doesn't matter"),
    (3, .., 4)  => println!("First is `3`, last is `4`, and the rest doesn't matter"),
    // `..` can be used to ignore the rest of the tuple
    _      => println!("It doesn't matter what they are"),
    // `_` means don't bind the value to a variable
  };
}

How can I do this in C#?

Pierre answered 26/12, 2022 at 19:42 Comment(1)
note that this answer, a possible duplicate, does not address destructuringPierre
P
3

Since C# 9.0, the language has supported Pattern Matching, overview of the pattern syntax can be found here.

Using this as a reference, it's easy to implement very similar functionality for each of the above cases in C#:

foreach (int n in Enumerable.Range(0, 101)) {
  Console.WriteLine((n % 5, n % 2, n % 7) switch {
    (0, var y, var z) => $"First is `0`, `y` is {y}, and `z` is {z}",
    (1, _, _) => "First is `1` and the rest doesn't matter",
    (_, _, 2)  => "last is `2` and the rest doesn't matter",
    (3, _, 4)  => "First is `3`, last is `4`, and the rest doesn't matter",
    _      => "It doesn't matter what they are",
  });
}

Couple things of particular note here:

  1. We must consume the result of the switch expression. For instance, this causes an error:
foreach (int n in Enumerable.Range(0, 101)) {
  // causes: 
  //   error CS0201: Only assignment, call, increment, 
  //       decrement, await, and new object expressions 
  //       can be used as a statement
  (n % 5, n % 2, n % 7) switch {
    (0, var y, var z) => Console.WriteLine($"First is `0`, `y` is {y}, and `z` is {z}"),
    (1, _, _)  => Console.WriteLine("First is `1` and the rest doesn't matter"),
    (_, _, 2)  => Console.WriteLine("last is `2` and the rest doesn't matter"),
    (3, _, 4)  => Console.WriteLine("First is `3`, last is `4`, and the rest doesn't matter"),
    _      => Console.WriteLine("It doesn't matter what they are"),
  };
}
  1. If we want to use .. to throw away 0 or more positional items, we'll need C# 11.0 or greater, and we'd need to instantiate it as a List or array instead, and switch to using square brackets [] rather than parentheses (), like so:
foreach (int n in Enumerable.Range(0, 101)) {
  // this can also be any of: 
  //      `new int[] { n % 5, n % 2, n % 7 }`, 
  //      `new[] { n % 5, n % 2, n % 7 }`, 
  //      `{ n % 5, n % 2, n % 7 }`
  Console.WriteLine(new List<int> { n % 5, n % 2, n % 7 } switch {
    [0, var y, var z] => $"First is `0`, `y` is {y}, and `z` is {z}",
    [1, ..] => "First is `1` and the rest doesn't matter",
    [.., 2] => "last is `2` and the rest doesn't matter",
    [3, .., 4] => "First is `3`, last is `4`, and the rest doesn't matter",
    _ => "It doesn't matter what they are",
  });
}
Pierre answered 26/12, 2022 at 19:42 Comment(3)
C# actually has a switch statement and a switch expression. You are using the latter here. Also, instead of String.Format, you could use String interpolation using $. Create an array: int[] input = { n % 5, n % 2, n % 7 };. Note that this not an array literal but an array initializer.Uraninite
Thanks for the good points, @OlivierJacot-Descombes I've incorporated what I think you've meant there. It doesn't seem like I've made the mistake of calling the initializer syntax a literal, but the difference between them is new to me! Explanation herePierre
You have not made any mistakes. It was meant as suggestions and clarifications. Maybe we will see this in C#12: Proposal: Collection literals #5354.Uraninite

© 2022 - 2024 — McMap. All rights reserved.