.NET 5 を使いたい理由6選(C#編)

# C#9.0の進歩が目覚ましいので使いたい

.NET5 がリリースされました。C#も9.0となりました。C#9.0は.NET5以降でしか使えません。
.NET5 を使えるプロジェクトではどんどん新しい文法を活用しましょう。
C#9.0の新機能から、素晴らしいと思った部分をまとめてみました。

## 使いたい理由1 : record

`class`, `struct` に加えて `record` を用いて型定義することができます。
変更不可能な参照型とのことです。

```C#
    public record Person // classのかわりにrecord
    {
        public string Name { get; }
        public DateTime BirthDate { get; }
        public Person(string name, DateTime birthDate)
        {
            Name = name;
            BirthDate = birthDate;
        }
    }
```
これをILSpyなどで覗いてみると以下のようなコードに展開されます。大方の予想通り class ですがリッチです。

```C#
    public class Person : IEquatable<Person>
    {
        protected virtual Type EqualityContract
        {
            [System.Runtime.CompilerServices.NullableContext(1)]
            [CompilerGenerated]
            get => typeof(Person);
        }
        public string Name { get; }
        public DateTime BirthDate { get; }
        public Person(string name, DateTime birthDate)
        {
            Name = name;
            BirthDate = birthDate;
        }

        public override string ToString()
        {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.Append("Person");
            stringBuilder.Append(" { ");
            if (PrintMembers(stringBuilder))
            {
                stringBuilder.Append(" ");
            }
            stringBuilder.Append("}");
            return stringBuilder.ToString();
        }

        protected virtual bool PrintMembers(StringBuilder builder)
        {
            builder.Append("Name");
            builder.Append(" = ");
            builder.Append((object?)Name);
            builder.Append(", ");
            builder.Append("BirthDate");
            builder.Append(" = ");
            builder.Append(BirthDate.ToString());
            return true;
        }

        [System.Runtime.CompilerServices.NullableContext(2)]
        public static bool operator !=(Person? r1, Person? r2) => !(r1 == r2);

        [System.Runtime.CompilerServices.NullableContext(2)]
        public static bool operator ==(Person? r1, Person? r2) => (object)r1 == r2 || (r1?.Equals(r2) ?? false);

        public override int GetHashCode() => (EqualityComparer<Type>.Default.GetHashCode(EqualityContract) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Name)) * -1521134295 + EqualityComparer<DateTime>.Default.GetHashCode(BirthDate);
        public override bool Equals(object? obj) => Equals(obj as Person);
        public virtual bool Equals(Person? other) => (object)other != null && EqualityContract == other!.EqualityContract && EqualityComparer<string>.Default.Equals(Name, other!.Name) && EqualityComparer<DateTime>.Default.Equals(BirthDate, other!.BirthDate);
        public virtual Person<Clone>$() => new Person(this); // Clone系の何からしいが不明

        protected Person(Person original)
        {
            Name = original.Name;
            BirthDate = original.BirthDate;
        }
    }

```
`ToString`や`GetHashCode`、比較演算子やコピーコンストラクタなどが自動的に実装されます。
比較演算子も「クラスのインスタンス」ではなく「クラスの内容が」等しいかという実装は大変にありがたいものです。

## 使いたい理由2 : init

先ほどのPersonのコンストラクタを省略します。代わりに init という初期化中だけ使える Setter を用いてみますがこれが大変良い働きをします。

```C#
    class Program
    {
        static void Main(string[] args)
        {
            Person person = new Person() { Name = "まだ名はない", BirthDate = DateTime.Today };
            Console.WriteLine(person);
        }
    }

    public record Person
    {
        public string Name { get; init; }
        public DateTime BirthDate { get; init; }
    }
```
重要なのは、コンストラクタを抜けた後のwith式内まで使用可能であることです。
当然ながら record が不変であることも合わせて変更はできません。

```C#
   person.Name = "ねこ"; // コンパイルエラー
```

ところで、不変なのにSetterが使えるとはどういうことでしょうか。気になります。
ILを見てみましょう。
NameのSetterは内部的には set_Name となります。第1引数に value を受け取っています。

```
	.method public hidebysig specialname 
		instance void modreq([System.Runtime]System.Runtime.CompilerServices.IsExternalInit) set_Name (
			string 'value'
		) cil managed 
	{
        // 省略
	}
```
`modreq`というのがポイントのようです。これは呼び出し元が無視してはならない制約という意味です。
外部初期化までは有効、つまり「IsExternalInit」という属性が制約として機能します。


## 使いたい理由3 : 型省略可能なnew()

new は型が明確である場合に型の記述を省略できるようになりました。プロパティやフィールドの初期化などで同じ型を2回書く必要はもうなくなりました。
ローカル変数でvarとこれのどちらを使うかは迷いどころです。

```C#
    Person person = new() { Name = "まだ名はない", BirthDate = DateTime.Today };
```

## 使いたい理由4 : パターンマッチングの強化

`is` のあとに `and` `or` `not` などを記述できるようになりました。

```C#
    // c is のあとは c に対する条件式
    public static bool IsLetterOrSpace(this char c) => c is >= 'a' and <= 'z' or >= 'A' and <= 'Z' or ' ';
    // 従来の場合 c を何度も書く必要があった
    public static bool IsLetterOrSpace(this char c) => c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || ' ';
```
同じものを記述する回数が減ることは基本的に良いことです。大いに活用したいと思います。

## 使いたい理由5 : staticラムダ式

メソッド内にこのように書くことができます。

```C#
    Func<double, double> square = static x => x * x;
```

意味が伝わりにくいかもしれませんが、ラムダ式はClosure(関数閉包)です。ラムダ式がローカル変数やthisを参照できるのは、関数オブジェクトとしてローカル変数やthisを内包しているからです。このキャプチャーと呼ばれる内包処理のために、通常のラムダ式はメソッド内で毎回 delegate を生成します。
しかし、thisもローカル変数も内包する必要のないラムダ式の場合、使用する都度生成する必要もなくなり、static で静的にラムダ式を保持するようになります。つまりその分はパフォーマンスが良いということになります。

しかしそれでは、このように書くのと何が違うのか？となります。

```C#
class Foo
{
    static Func<double, double> Square = x => x * x;
}
```
スコープを絞れるメリットがあります。ローカルメソッドのように、そのメソッド内部以外から見えないということは重要です。

## 使いたい理由6 : 拡張メソッドでもGetEnumeratorがあればforeachできる
GetEnumeratorを拡張メソッドで用意すれば、Rangeですらこのように拡張できてしまいます。これはとても強力です。

```C#
    class Program
    {
        static void Main(string[] args)
        {
            foreach (var index in 1..10)
                Console.WriteLine(index);
        }
    }
    public static class RangeExtensions
    {
        public static IEnumerable<int> GetEnumerable(this Range range)
        {
            for (int idx = range.Start.Value; idx <= range.End.Value; idx++)
                yield return idx;
        }
        public static IEnumerator<int> GetEnumerator(this Range range)
            => range.GetEnumerable().GetEnumerator();
    }
```
パターンマッチングと組み合わせて何かができそうな気がします。


## その他の強化

`unsafe` `unmanaged` 関連の補強も興味深いですが、今回の記事では扱わないことにします。

## 謝辞

ここで扱った情報はMicrosoftのサイト( https://docs.microsoft.com/ja-jp/dotnet/csharp/whats-new/csharp-9 )の内容から、これはぜひ使いたいと思った機能を抽出し、自分なりに分析を加えたものです。
.NET と C# の発展に携わるすべてのエンジニアの皆様に感謝いたします。

## ご案内

この記事は以下の連載記事の第2回目となります。
https://qiita.com/proprogrammer0/items/560ffaf99cdf828c8e52 「.NET 5 を使いたい理由6選」
第3回目記事はTwitterの同名アカウント( https://twitter.com/proprogrammer0 )上から案内予定です。ASP.NET CoreやEF Coreにも触れる予定です。よろしければご覧ください。