C# 小ネタ：.NET 5.0 で使える C# 9.0 で気に入ってる機能紹介

C# 9.0 がリリースされるまで秒読みになりましたね！前に C# 8.0 風書き方アレコレを書いてみましたが、9.0 になってしまうと、また新機能が追加されてより良い言語になっていきます。

[C# 小ネタ：C# 8.0 風の書き方アレコレ](https://qiita.com/okazuki/items/fd81456a6e9902bf3a63)

C# 9.0 は .NET 5.0 で使えるので LTS 版じゃないので仕事で本格的に使える人は少ないかもしれませんが LTS の .NET 6.0 に計画的に移行するように考えて使うというのはありかなぁと思います。
まぁ、そこらへんは置いといて新しいものがでたら趣味では試したい！！ということでやってみましょう。

## レコード型

多分、今回追加された中で個人的に一番使いそうな機能です。
イミュータブルなクラスを定義するのが凄く簡単になります。

X と Y というプロパティを持った Point 型は以下のように定義できます。

```csharp
record Point(int X, int Y);
```

ちょっと使ってみましょう。

```csharp
using System;

namespace ConsoleApp18
{
    class Program
    {
        static void Main(string[] args)
        {
            // クラスと同じ感じで new して使って
            var p1 = new Point(10, 20);
            // プロパティにアクセスできて
            Console.WriteLine($"{p1.X}, {p1.Y}");
            // ToString もいい感じに表示される
            Console.WriteLine(p1.ToString());
        }
    }

    record Point(int X, int Y);
}
```

実行するとこんな感じに表示されます。

```
10, 20
Point { X = 10, Y = 20 }
```

特徴の 1 つはプロパティは読み取り専用で書き換え不可能な点です。

```csharp
var p1 = new Point(10, 20);
p1.X = 100; // エラー
```

一部分だけ値を書き換えたいときは with 式という、これもまた新しい機能で一部分だけ書き換えたコピーを簡単に作れます。他にも分解や比較も出来ます。

```csharp
// クラスと同じ感じで new して使って
var p1 = new Point(10, 20);
// X を 100 にしたコピーを作成
var p2 = p1 with { X = 100 };

// 分解も対応
var (x, y) = p1;

// プロパティ同士の値を使って比較もしてくれる
if (p1 == p2)
{
    
}
```

レコード型は自分でプロパティを定義したりメソッドも定義できます。

```csharp
record Point
{
    public int X { get; init; }
    public int Y { get; init; }

    public void Show() => Console.WriteLine($"({X}, {Y})");
}
```

プロパティに set のかわりに init をつけることでオブジェクト初期化子だけで設定可能なプロパティも指定できます。これはレコードだけじゃなくてクラスや構造体でもいけます。

```csharp
// OK
var p1 = new Point { X = 100, Y = 200 };
// これはエラー
p1.X = 200;
// プロパティの値を変えたものが欲しい場合は上で紹介したのと同じで with でコピーを作る
var p2 = p1 with { X = 999 };
```

継承も OK

```csharp
record Point(int X, int Y);
record Point3D(int X, int Y, int Z) : Point(X, Y);
```

レコードはいいですね。いい。

## トップレベルステートメント

これはコンソールアプリとかの Main メソッドを定義しなくてもいい感じにやってくれる奴。こいつもいいやつ。
これを使うとハローワールドがこうなる。最高

```csharp
using System;
Console.WriteLine("Hello world");
```

ASP.NET Core のハローワールド返すだけの Web アプリのコードとかもこんなにすっきりします。

```csharp
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

Host.CreateDefaultBuilder(args)
    .ConfigureWebHostDefaults(builder =>
        builder.Configure(app =>
        {
            app.UseRouting();
            app.UseEndpoints(
                endpoints => endpoints.MapGet(
                    "/", 
                    context => context.Response.WriteAsync("Hello world"))
            );
        })
    ).Build().Run();
```

## null じゃないんですよを簡単に書ける

シンプルだけど可読性という点ではいい。`is not null` って書ける。

```csharp
using System;

object o = null;

// 古き良き書き方
if (o != null)
{
    // null じゃない
}

// C# 8.0 だけとこう書けるけど知らないとわかりにくい
if (o is { })
{
    // null じゃない
}

// C# 9.0
if (o is not null)
{
    // null じゃない
}
```

## 大なり小なりの比較も簡単に

これは、もしかしたら賛否ありそう。でも、予備知識なしだと新しい書き方のほうが読みやすいだろうなぁ。慣れてる人はそうでもないかも。

```csharp
using System;

var x = 15;

// 今まで
if (x >= 10 && x <= 20)
{
    Console.WriteLine("10 以上で20 以下");
}

// C# 9.0
if (x is >= 10 and <= 20)
{
    Console.WriteLine("10 以上で20 以下");
}

// 今まで
if (x >= 10 && x <= 20 || x >= 100)
{
    Console.WriteLine("10 以上で20 以下もしくは 100 以上");
}

// C# 9.0
if (x is >= 10 and <= 20 or >= 100)
{
    Console.WriteLine("10 以上で20 以下もしくは 100 以上");
}
```

### 追記

@albireo さんがコメントで以下のコード例を書いてくれました。確かに…！

新しい大なり小なりは、比較対象が長い名前や深いプロパティ階層になってしまうときにありがたみが実感できそうです。

```csharp
//長い名前だとよく似た別のプロパティをうっかり比較してても気づきにくい
if (datagridview.CurrentCellAddress.X >= 0 && datagridview.CurrentCellAddress.Y <= 10) { }

//それを防ぐためにローカル変数を使うのもすっきりしない
var currentX = datagridview.CurrentCellAddress.X;
if (curretnX >= 0 && currentX <= 10) { }

//すっきり
if (datagridview.CurrentCellAddress.X is >= 0 and <= 10) { }



//メソッドの返り値が範囲内かどうかを知りたいだけなのにいったんローカル変数に格納
var result = func();
if (result >= 0 && result <= 10) { }

//これだとメソッドを二回実行してしまう
if (func() >= 0 && func() <= 10) { }

//返り値の範囲チェックだけならこれでOK
if (func() is >= 0 and <= 10) { }
```

## Target-typed new 式

日本語の正しい訳がわからない…けど型が明らかな時には new 演算子で型名を省略できる便利機能。

例えば…

```csharp
// 右辺に型が書いてるので省略できる
Person p = new();
// ↑は個人的には var かも
var p = new Person();

// メソッドの戻り値の型はわかってるので省略できる
record Person(string Name);

Person foo()
{
    // なんか処理
    return new("Tanak");
}

// メソッドの引数の型はわかってるので省略できる
record Person(string Name);
void foo(Person p)
{
    // なんか処理
}

foo(new("Tanaka")); // PropertyChangedEventArgs の new とかで使いそう
```

## まとめ

C# 9.0 の新機能をまとめた公式ドキュメントは以下のページにあります。

[C# 9.0 の新機能](https://docs.microsoft.com/ja-jp/dotnet/csharp/whats-new/csharp-9#pattern-matching-enhancements)

まだ、新しいページなので機械翻訳だから残念な部分もあるので読める人は英語で目を通すことをお勧めします。そのうち人による翻訳になるといいなぁ…

公式じゃないところだと安定の岩永さんのサイトもお勧め。まだ網羅はされてないみたいだけど、そのうち…？

https://ufcpp.net/study/csharp/cheatsheet/ap_ver9/

C# 言語自体の機能じゃないけどソースジェネレーターというソースコード生成の仕組みも入ったので、みんな大好き Excel VBA マクロでソースコード吐き出してる人は移行先としていいかも？？

ということで、来週のリリースが楽しみですね！！