C# 9.0 新機能 「レコード（record）型 / with 式」


今更ながらC#9.0から追加されたレコード型と`with`式を使ってみたのでメモ
https://docs.microsoft.com/ja-jp/dotnet/csharp/whats-new/tutorials/records

# 前提

例えば、平面上の座標を示す`Point2D`クラスのインスタンスが複数存在する時に、
あるインスタンスが他のインスタンスと**内容（X座標とY座標の値）が等しいか**を判定したいとします。

```cs
public class Point2D
{
    public int X { get; }
    public int Y { get; }
    public Point2D(int x, int y) => (X, Y) = (x, y);
}

public class Program
{
    private static void Main(string[] args)
    {
        var src = new Point2D(10, 20);

        var p1 = new Point2D(5, 10);
        var p2 = new Point2D(10, 20);

        //Point2D のインスタンス src が 他のインスタンス p1,p2 と
        //内容（X座標とY座標の値）が等しいかを判定したい
    }
}
```

# レコードを利用しない場合（～C#8.0）

参照先ではなく、内容が等しいかを判定するためには
C#8.0までは`Point2D`クラスで`Equals`メソッドと`GetHashCode`メソッドをオーバーライドする必要がありました。

```cs
public class Point2D
{
    public int X { get; }
    public int Y { get; }
    public Point2D(int x, int y) => (X, Y) = (x, y);

    //X座標とY座標の値で等価比較をするようにオーバーライド
    public override bool Equals(object obj)
    {
        if (obj == null || obj.GetType() != GetType()) return false;
        var p = (Point2D)obj;
        return ReferenceEquals(this, p) || 
               X == p.X && Y == p.Y;
    }
    
    //X座標とY座標の値からハッシュ値を生成するようにオーバーライド
    public override int GetHashCode() => HashCode.Combine(X, Y);
}

public class Program
{
    private static void Main(string[] args)
    {
        var src = new Point2D(10, 20);

        var p1 = new Point2D(5, 10);
        var p2 = new Point2D(10, 20);

        //Point2D のインスタンス src が 他のインスタンス p1,p2 と
        //内容（X座標とY座標の値）が等しいかを判定したい
        Console.WriteLine($"a.Equals(p1)：{src.Equals(p1)}"); // False
        Console.WriteLine($"a.Equals(p2)：{src.Equals(p2)}"); // True
    }
}
```

# レコードを利用した場合（C#9.0～）

C#9.0からはレコード型を利用することによって、より簡単に実装することができます。
レコードを定義するには、`class`の代わりに`record`キーワードを使用して型を宣言します。

```cs
 public record Point2D(int X, int Y);
```

レコードは、クラスと同じように扱うことができます。もちろんメンバを定義することもできます。

```cs
public record Point2D(int X, int Y)
{
    public int Property { get; }

    public Point2D(int x, int y, int property) 
        : this(x, y)
    {
        Property = property;
    }

    public void Method()
    {
        Console.WriteLine("foo");
    }
}
```


そして、主に以下の作業を内部的に実施してくれます。

* 型名に続く（）で渡した内容で、`get/init`なプロパティを生成
    - `public int X { get; init; }`
    - `public int Y { get; init; }`
* 各プロパティの値を引数に取るコンストラクタの生成
* `Equals`メソッドをオーバーライド
    - 各プロパティの値（ここではX座標とY座標の値）で等価比較をする
* `GetHashCode`メソッドをオーバーライド
    - 各プロパティの値からハッシュ値を生成する
* `ToString`メソッドをオーバーライド
    - `レコード名 { プロパティ名 = 値, ... }`の形式で文字列化する

結果として、あるインスタンスが他のインスタンスと**内容が等しいか**を判定したい時にレコード型を利用すると、今までと比べて簡単に実装することが可能です。

```cs
public record Point2D(int X, int Y);

public class Program
{
    private static void Main(string[] args)
    {
        var src = new Point2D(10, 20);

        var p1 = new Point2D(5, 10);
        var p2 = new Point2D(10, 20);

        //Point2D のインスタンス src が 他のインスタンス p1,p2 と
        //内容（X座標とY座標の値）が等しいかを判定したい
        Console.WriteLine($"a.Equals(p1)：{src.Equals(p1)}"); // False
        Console.WriteLine($"a.Equals(p2)：{src.Equals(p2)}"); // True
        
		//ToStringもオーバーライドされている
		Console.WriteLine(src); //Point2D { X = 10, Y = 20 }
    }
}
```

# レコードの複製（with 式）

レコード型と同じくC#9.0から追加された`with`式を使うと、簡単にレコードの複製をすることができます。

`コピー先 = コピー元 with { プロパティ名 = 値, ... }`

```cs
public record Point2D(int X, int Y);

public class Program
{
    private static void Main(string[] args)
    {
        var src = new Point2D(10, 20);
        
        var copy = src with { X = 30, Y = 60 };

        Console.WriteLine(copy); // Point2D { X = 30, Y = 60 }
        Console.WriteLine(src);  // Point2D { X = 10, Y = 20 }
    }
}
```

以上です。