[C#] Play with switch
Tags: csharp
Published: 2020-11-28 01:21:03

## Intro
In C# 7 and C# 8, "switch" got some new features.
In this time, I will try using "switch".

### Environments
* .NET 5.0.100

## Before C# 7 (Constant Patterns)
#### Program.cs
```cs
class Program
{
    static void Main(string[] args)
    {
        int id = 1;
        switch(id)
        {
            case 0:
                Console.WriteLine("Id was 0");
                break;
            case 1:
                Console.WriteLine("Id was 1");
                break;
            default:
                Console.WriteLine("Not found");
                break;
        }
    }
}
```
Only constant values can be used.

## New Patterns
### Type Patterns
After C# 7, I can use type.
In type pattern, after checking if a target object can be converted, converted instance will be created.

#### RecordSample.cs
```cs
    public record RecordSample(int Id, string Name);
```
#### Program.cs
```cs
    static void Main(string[] args)
    {
        RecordSample r = new (3, "Hello"); 
        Play((object)r);
    }
    private static void Play(object target)
    {
        switch(target)
        {
            case RecordSample r:
                Console.WriteLine($"Record Id: {r.Id} Name: {r.Name}");
                break;
            default:
                Console.WriteLine("Not found");
                break;
        }
    }
```
#### Result
```
Record Id: 3 Name: Hello
```

### Tuple Patterns
#### Program.cs
```cs
        static void Main(string[] args)
        {
            (int Id, string Name) t = (0, "Hello");
            Play((object)t);
        }
        private static void Play(object target)
        {
            switch(target)
            {
                case RecordSample r:
                    Console.WriteLine($"Record Id: {r.Id} Name: {r.Name}");
                    break;
                case (0, "Hello"):
                    Console.WriteLine("Tuple");
                    break;
                default:
                    Console.WriteLine("Not found");
                    break;
            }
        }
```
#### Result
```
Tuple
```

### Property Patterns
#### Program.cs
```cs
        static void Main(string[] args)
        {
            var r = new RecordSample(1, "Hello");
            Play((object)r);
        }
        private static void Play(object target)
        {
            switch(target)
            {
                case RecordSample { Id: 1} r:
                    Console.WriteLine($"Property pattern Id: {r.Id} Name: {r.Name}");
                    break;
                case RecordSample r:
                    Console.WriteLine($"Record Id: {r.Id} Name: {r.Name}");
                    break;
                default:
                    Console.WriteLine("Not found");
                    break;
            }
        }
```
#### Result
```
Property pattern Id: 1 Name: Hello
```

I also can use some more patterns(ex. Positional Patterns). 

### when
In new Patterns(ex. Type Patterns), I can separate into more specific conditions.

#### Program.cs
```cs
        static void Main(string[] args)
        {
            var r = new RecordSample(1, "Hello");
            Play((object)r);
        }
        private static void Play(object target)
        {
            switch(target)
            {
                case RecordSample r when r.Id < 0:
                    Console.WriteLine($"Record1 Id: {r.Id} Name: {r.Name}");
                    break;
                case RecordSample r when r.Id >= 1:
                    Console.WriteLine($"Record2 Id: {r.Id} Name: {r.Name}");
                    break;
                case RecordSample r:
                    Console.WriteLine($"Record Id: {r.Id} Name: {r.Name}");
                    break;
                default:
                    Console.WriteLine("Not found");
                    break;
            }
        }
```
#### Result
```
Record2 Id: 1 Name: Hello
```

## switch expression
In C# 8, "switch expression" was added.

I can refactor like below.

### Before
#### Program.cs
```cs
        static void Main(string[] args)
        {
            int id = 1;
            string message = GetMessage(id);
            Console.WriteLine(message);
        }
        private static string GetMessage(int id)
        {
            switch(id)
            {
                case 0:
                    return "ID was 0";
                case 1:
                    return "ID was 1";
                default:
                    return "Other";
            }
        }
```
#### Result
```
ID was 1
```
### After
#### Program.cs
```cs
        static void Main(string[] args)
        {
            int id = 1;
            string message = id switch
            {
                0 => "ID was 0",
                1 => "ID was 1",
                _ => "Other"
            };
            Console.WriteLine(message);
        }
```
#### Result
```
ID was 1
```

I can use same patterns as the switch statement.

### Resources
* [Pattern Matching - C# Guide | Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/csharp/pattern-matching)
* [C# 8.0 - Pattern Matching in C# 8.0 | Microsoft Docs](https://docs.microsoft.com/en-us/archive/msdn-magazine/2019/may/csharp-8-0-pattern-matching-in-csharp-8-0)
* [C# switch statement | Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/switch)
* [switch expression - C# reference | Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/switch-expression)
* [Do more with patterns in C# 8.0 | .NET Blog](https://devblogs.microsoft.com/dotnet/do-more-with-patterns-in-c-8-0/)
* [Programming C# 8.0](https://www.oreilly.com/library/view/programming-c-80/9781492056805/)
* [https://ufcpp.net/study/csharp/datatype/patterns/](パターン マッチング - C# によるプログラミング入門 | ++C++; // 未確認飛行 C)
* [C# 8.0 パターン マッチング | ++C++; // 未確認飛行 C ブログ](https://ufcpp.net/blog/2018/12/cs8patterns/)

## How many instances are created?
### Question
For example, when I execute this code, how many instances are created?

#### Program.cs
```cs
        static void Main(string[] args)
        {
            var r = new RecordSample(0, "Hello");
            Play((object)r);
        }
        private static void Play(object target)
        {
            switch(target)
            {
                case RecordSample r when r.Id < 0:
                    Console.WriteLine($"Record1 Id: {r.Id} Name: {r.Name}");
                    break;
                case RecordSample r when r.Id >= 1:
                    Console.WriteLine($"Record2 Id: {r.Id} Name: {r.Name}");
                    break;
                case RecordSample r:
                    // Execute here
                    Console.WriteLine($"Record Id: {r.Id} Name: {r.Name}");
                    break;
                default:
                    Console.WriteLine("Not found");
                    break;
            }
        }
```
### Answer
Only one instance is created.

### Decompile
I decompile this code by ILSpy(https://marketplace.visualstudio.com/items?itemName=icsharpcode.ilspy-vscode).

```cs
private static void Play(object target)
{
	RecordSample recordSample = target as RecordSample;
	if ((object)recordSample != null)
	{
		if (recordSample.Id < 0)
		{
			Console.WriteLine($"Record1 Id: {recordSample.Id} Name: {recordSample.Name}");
			return;
		}
		RecordSample recordSample2 = recordSample;
		if (recordSample2.Id >= 1)
		{
			Console.WriteLine($"Record2 Id: {recordSample2.Id} Name: {recordSample2.Name}");
			return;
		}
		RecordSample recordSample3 = recordSample;
		Console.WriteLine($"Record Id: {recordSample3.Id} Name: {recordSample3.Name}");
	}
	else
	{
		Console.WriteLine("Not found");
	}
}
```

1. A instance is created by "as RecordSample"
2. If it isn't null, use the if statement to perform the operation under the condition of the when clause.

So only one instance is created.

## Convert an object like TypeScript
In TypeScript, I can convert if an instance has same property as RecordSample class.

#### record-sample.ts
```ts
export class RecordSample {
    public id: number = -1;
    public name: string = "";
}
```
#### program.ts
```ts
import { RecordSample } from "./record-sample";

export function main(){
    const s = {
        id: 1,
        name: "Hello"
    };
    const r: RecordSample = s; // <- OK
}
```
I also want to convert like this in C#.

First, I add a method to convert.

#### RecordSample.cs
```cs
    public record RecordSample
    {
        public int Id { get; init; }
        public string Name { get; init; } = "";
        public RecordSample(int id, string name)
        {
            Id = id;
            Name = name;
        }
        public static bool TryParse(object original, out RecordSample? result)
        {
            result = null;
            var type = original.GetType();
            var idValue = type.GetProperty("Id")?.GetValue(original)?.ToString();
            var nameValue = type.GetProperty("Name")?.GetValue(original)?.ToString();
            if(idValue == null || nameValue == null)
            {
                return false;
            }
            if(int.TryParse(idValue.ToString(), out var id) == false)
            {
                return false;
            }
            result = new RecordSample(id, nameValue);
            return true;
        }
    }
```

* [Object.GetType Method (System) | Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/api/system.object.gettype?view=net-5.0)
* [Type.GetProperty Method (System) | Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/api/system.type.getproperty?view=net-5.0)

And I add this into the switch statement.

```cs
        private static void Play(object target)
        {
            switch(target)
            {
                case RecordSample r:
                    Console.WriteLine($"Record Id: {r.Id} Name: {r.Name}");
                    break;
                case var o when RecordSample.TryParse(o, out var r):
                    Console.WriteLine($"Property Id: {r!.Id} Name: {r!.Name}");
                    break;
                default:
                    Console.WriteLine("Not found");
                    break;
            }
        }
```

"var" in switch statements can catch all conditions.
And check target's properties by when clause.

#### Program.cs
```cs
        static void Main(string[] args)
        {
            var r = new {
                Id = 0,
                Name = "Hello"
            };
            Play((object)r);
        }
        private static void Play(object target)
        {
            switch(target)
            {
                case RecordSample r:
                    Console.WriteLine($"Record Id: {r.Id} Name: {r.Name}");
                    break;
                case var o when RecordSample.TryParse(o, out var r):
                    Console.WriteLine($"Property Id: {r!.Id} Name: {r!.Name}");
                    break;
                default:
                    Console.WriteLine("Not found");
                    break;
            }
        }
```

#### Result
```
Property Id: 0 Name: Hello
```