C# equivalent of rusts "match"
Asked Answered
c#
R

5

7

In Rust there is a handy statement called match. It works similar to switch, however there can be multiple variables.

Here is a pseudo-example doing fizz-buzz using this technique:

match (i % 3 == 0, i % 5 == 0) {
    (false, false)  =>  { // },
    (true, true)    =>  { // FizzBuzz },
    (true, false)   =>  { // Fizz },
    (false, true)   =>  { // Buzz }
}

Is it possible to do same or something similar using C# 7?

Riot answered 10/5, 2017 at 15:10 Comment(7)
No, C# doesn't have a feature like this. You could probably emulate it to some extent using a lambda, but I don't really have time to come up with a solution, thus i'm posting this as a comment. Maybe it will give someone else the idea to figure it out.Tetzel
OK, this is sufficient @ErikFunkenbuschRiot
The problem is that in C#, switch statements require constants, and there isn't any way to define a multi-value constant in C# to match against. You could convert the tested fields into text results, and then do a string match on them, but that's a bit hackish.Tetzel
@ErikFunkenbusch: as of C# 7, a case statement in a switch can be a type, with a variable declaration and a when clause to conditionally test for specific values. Unfortunately, the tuple syntax isn't recognized in that context, i.e. you can't write case (bool, bool) t when..., but instead have to write case ValueTuple<bool, bool> t when.... (Though oddly enough, you can write switch ((i % 3 == 0, i % 5 == 0))). But otherwise, that approach would work here (though IMHO even if the tuple syntax was recognized in a case, a dictionary would be more concise).Forename
@PeterDuniho - that's fantastic, and I did in fact find a solution here that works.Tetzel
Please see @ErikFunkenbusch 's answer. Looks almost exactly the same, therefore I'd be willing to say the functionality exists.Sheelah
This question is outdated, if you found this when using C# 9.0 or above, see my question that includes destructuring hereEastlake
F
3

I would use the tuple syntax in C# 7 for this:

class Program
{
    enum FizzBuzz
    {
        None,
        Fizz,
        Buzz,
        FizzBuzz
    }

    static void Main(string[] args)
    {
        // Declare the map
        Dictionary<(bool, bool), FizzBuzz> matchMap =
            new Dictionary<(bool, bool), FizzBuzz>
            {
                {  (false, false), FizzBuzz.None },
                {  (true, true), FizzBuzz.FizzBuzz },
                {  (true, false), FizzBuzz.Fizz },
                {  (false, true), FizzBuzz.Buzz },
            };

        // Demonstration of the map
        for (int i = 0; i < 16; i++)
        {
            Console.WriteLine($"i: {i}, (i % 3 == 0, i % 5 == 0): {matchMap[(i % 3 == 0, i % 5 == 0)]}");
        }
    }
}

For now, you need to use the NuGet Package Manager to add the ValueTuple package to your project for the syntax to compile. The ValueTuple types implement the appropriate equality comparison to allow the dictionary to work.

Forename answered 10/5, 2017 at 15:35 Comment(4)
That's interesting. I hadn't read up on ValueTuple's yet, and I got excited that I could use them as a constant, but that appears to not be the case. So, for example, I can't switch on a ValueTuple and use a ValueTuple as a case. It appears to be more syntactic sugar than real changes to the language.Tetzel
@ErikFunkenbusch: "syntactic sugar" is still a "real change to the language". A lot of what high-level languages like C# do is simply "syntactic sugar". For example, LINQ is arguably "just syntactic sugar".Forename
That said, yes...the tuple support in C# is syntactic sugar. It does nothing that you couldn't do explicitly in the language, and once compiled, there's very little left as evidence that the tuple support feature was used. Note that the inability to switch on a tuple has to do with lack of integration between that feature and the other new feature, pattern matching. You can use ValueTuple explicitly in a case for that scenario, even while the compiler won't recognize a tuple type declaration that would otherwise be valid elsewhere.Forename
What I meant by "real language change" was that it changed something fundamental about way the language works, not just translated it to an existing language construct that might be more verbose. But yeah, it looks like the pattern matching feature is just such a change.Tetzel
T
1

Thanks to @PeterDuniho for pointing out a way to make this work. The following code (while not as succinct and clean as the rust code) works in C# 7.

switch((i % 3 == 0, i % 5 == 0))
{
case ValueTuple<bool, bool> t when (t.Item1 == true && t.Item2 == true):
// FizzBuzz    
    break;
case ValueTuple<bool, bool> t when (t.Item1 == true && t.Item2 == false):
// Fizz
    break;
case ValueTuple<bool, bool> t when (t.Item1 == false && t.Item2 == true):
// Buzz    
    break;
}

You could probably shorten the above even more by using logical shortcuts like this:

switch((i % 3 == 0, i % 5 == 0))
{
case ValueTuple<bool, bool> t when (t.Item1 && t.Item2):
// FizzBuzz    
    break;
case ValueTuple<bool, bool> t when (t.Item1):
// Fizz
    break;
case ValueTuple<bool, bool> t when (t.Item2):
// Buzz    
    break;
}

This would work because it would only ever get to evaluate the second and third arguments if the previous one was false. Of course this only really works in a simple Boolean situation like this.

Also, i'd be wary about property evaluation here with side-effects. You shouldn't design properties to have side-effects, but if you do this might evaluate the properties multiple times, causing the side-effects to applied multiple times (ie, say your property adds or decrements a value each time it's called).

Tetzel answered 10/5, 2017 at 19:17 Comment(3)
Actually, no. The code compiles fine as is. you don't need the extra parens. You're right about the ==. The first version is simply intended to be explicit, that's why I gave the second version, which is a better choice.Tetzel
@PeterDuniho - Strange, looks like I am wrong again. I swear it compiled without them when I was optimizing it, but now it doesn't.. so I must have made a mistake somewhere.Tetzel
This looks exactly like rusts match in c# syntax. I'd say this is the answer.Sheelah
I
1

These switch expressions can be used since C# 8

return (i % 3 == 0, i % 5 == 0) switch
{
    ValueTuple<bool, bool> t when (t.Item1 && t.Item2) => "FizzBuzz",
    ValueTuple<bool, bool> t when (t.Item1) => "Fizz",
    ValueTuple<bool, bool> t when (t.Item2) => "Buzz",
    _ => "",
};
Integration answered 19/4, 2023 at 14:13 Comment(1)
Note that the question specifically asks for a C# 7 solution.Alveolate
O
0

In C#7, you could write an extension method to more or less fake that syntax with LINQ using ValueTuple:

class Program
{
    static void Main()
    {
        var fb =
            Enumerable.Range(0, 20)
            .Select(n => ((n % 3 == 0), (n % 5 == 0)))
            .Match(
                ((true, true), () => "FizzBuzz"),
                ((true, false), () => "Fizz"),
                ((false, true), () => "Buzz")
            );

        Console.WriteLine(String.Join("\n", fb));

        Console.ReadKey();
    }
}

public static class Extensions
{
    public static IEnumerable<TResult> Match<TInput, TResult>(
        this IEnumerable<TInput> e, 
        params ValueTuple<TInput, Func<TResult>>[] cases)
    {
        return
            from evalue in e
            join @case in cases on evalue equals @case.Item1
            select @case.Item2();
    }
}

I'd question the sanity of anybody who did that in production code, but I'll keep it in mind for my next job interview.

Or you could return to the paleolithic:

switch ((n % 3 == 0 ? 1 : 0) | ((n % 5 == 0) ? 2 : 0))
{
    case 1:
        //"Fizz";
        break;
    case 2:
        //"Buzz";
        break;
    case 3:
        //"FizzBuzz";
        break;
}
Ophir answered 10/5, 2017 at 17:25 Comment(0)
F
0

Adding on to @ErikFunkenbusch's answer using ValueTuples, we can further utilize this feature to access ValueTuple items by name, rather than using Item1, Item2, etc.:

(bool IsDivisibleBy3, bool IsDivisibleBy5) match = (i % 3 == 0, i % 5 == 0);
switch (match)
{
    case var _ when match.IsDivisibleBy3 && match.IsDivisibleBy5:
        // FizzBuzz
        break;
    case var _ when match.IsDivisibleBy3:
        // Fizz
        break;
    case var _ when match.IsDivisibleBy5:
        // Buzz
        break;
    default:
        //
        break;
}
Frei answered 10/5, 2017 at 23:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.