息抜き C# ~ New Normal なコードの書き方:第06回「レコード型」 ~

こんにちはecbeingでアーキテクトをやっている宮原です。

New Normal なコードの書き方 の第06回目、今日は「レコード型」の書き方についてご紹介しようと思います。

本記事は 息抜きC# 記事の第06回目です。
第05回目「Hello World!」はこちら。

レコード型とは

レコード型をざっくり一言で説明すると「データ専用の型」です。
ある特定の型を指す物ではなく、「record」キーワードを使用して定義された型のことをレコード型*1といいます。

レコード型はデータ格納のための専用型なので、それにふさわしい便利な機能がいろいろ揃っています。

レコード型の便利な機能

短く簡単な定義

// 【注目!】「Person」レコード型を定義する(これだけ!!)
record Person(string Name, int Age, string Address);

class Program
{
    public static void Main()
    {
        // 「Person」レコード型の「hoge」を作成する
        var hoge = new Person("ほげ山ほげ太郎", 17, "渋谷区宮益坂");
        // 「hoge」を使う
        Console.WriteLine($"{hoge.Name}{hoge.Age}歳です。");
    }
}


上記のコードの通り、レコード型はインスタンスの生成方法・使用方法は普通のクラスと異なりませんが、定義方法はかなり簡潔かつ特殊なものになっています。


レコード型の定義は型の定義と言うよりインターフェイスのメソッド定義のように見えます。この記法は「プロパティ定義の位置指定構文*2」と呼ばれ、引数の定義のような(string Name, int Age, string Address)の部分はコンストラクタの引数の定義*3であり、また同時にプロパティの定義となっています(この記述だけで「Name」「Age」「Address」の定義を行っています*4


この記法の採用により、レコード型は非常に少ない記述で型を定義することが可能となっており*5、コーディング時の労力の削減、可読性の向上、コーディングミスの低減など多くのメリットがあります。

パブリックなプロパティ

「プロパティ定義の位置指定構文」で定義されたプロパティたちは全てパブリックとなり、外部に公開されます*6
これは単純なデータ専用型のメンバーは、パブリックである方が自然なためです*7

自動実装される等価判定

クラスの場合、「==」も「Equals」も「参照先が等しいか」という判定しかしてくれませんが、レコード型の「==」と「Equals」は「型と全てのプロパティ値が等しいか」という判定を行ってくれます。非常に便利ですね。

自動実装されるToString()

var hogeClass = new ClassPerson("ほげ山ほげ太郎", 17, "渋谷区宮益坂");   // クラス
var hogeRecord = new RecordPerson("ほげ山ほげ太郎", 17, "渋谷区宮益坂"); // レコード

Console.WriteLine(hogeClass);  // 出力:ClassPerson
Console.WriteLine(hogeRecord);  // 出力:RecordPerson { Name = ほげ山ほげ太郎, Age = 17, Address = 渋谷区宮益坂 }

説明不要ですね。とても便利*8

レコード型その他の特徴

内部的には普通のクラス

色々と便利な機能があって定義も簡単なレコード型ですが、実は内部的には通常のクラスと変わりありません。
レコード型の定義は一種の糖衣構文であり、データ専用型として必要な機能が内部で自動生成されるのです。

【レコード型で自動生成されるものたち】

  • コンストラクタ
  • Equals()
  • ==
  • !=
  • GetHashCode()
  • ToString()
  • Deconstruct()*9

イミュータブル

レコード型は一度生成されるとその値を変更できない「イミュータブル」な性質を持ちます。

record Person(string Name, int Age, string Address);

class Program
{
    public static void Main()
    {
        var hoge = new Person("ほげ山ほげ太郎", 17, "渋谷区宮益坂");
        hoge.Name = "ほげ山ほげ次郎";  // 変更できないのでエラーが出る
    }
}

※ちなみに、レコード構造体型は「ミュータブル(変更可能)」です。

with式

イミュータブルな変数は内部の値を変更することができませんが、それでも時と場合により中身を書き換えたくなるものです。
そういった場合によく採用されるのが変数を複製し、一部を書き換えることで変更を擬似的に実現する機能です。
レコード型はこの「複製し、一部を書き換える」機能として「with式」というものが用意されました。

record Person(string Name, int Age, string Address);

class Program
{
    public static void Main()
    {
        var hoge = new Person("ほげ山ほげ太郎", 17, "渋谷区宮益坂");
        hoge = hoge with { Name = "ほげ山ほげ次郎" };  // 変更ができる
        // ※実際はhogeを複製し、Nameのみ書き換えている
    }
}

まとめ

このようにレコード型は

  • 簡潔な定義
  • データ専用型にふさわしい様々なメソッド
  • 一部を書き換え可能な複製式

などを備え、データコンテナとして今後活躍が期待される機能です。

今まであなたのプロジェクトでもデータ専用型のために大量のボイラープレートコード*10を書いていませんでしたか?
あなたのプロジェクトでもレコード型を採用し、大量のボイラープレートをプロダクトコードから取り除きましょう!



第07回目「生文字列リテラル」はこちら。

ecbeingではボイラープレートを減らしたいエンジニアを募集しています!!

careers.ecbeing.tech

*1:レコード型にはC#9.0で導入されたレコードクラス型と、C#10.0で導入されたレコード構造体型があります。この記事では基本的にレコードクラス型について言及しています

*2:英語では「Positional syntax for property definition

*3:コンストラクタの中身の処理は引数の定義から自動実装されます(仮引数をプロパティに設定するだけ)

*4:なお、この記法を「プライマリコンストラクタ」と呼んだりもします

*5:なお、従来のクラスのように中括弧で囲まれた定義も可能であり、その場合には細かいカスタマイズや(あまり推奨はしませんが)メソッドの定義が可能になります

*6:カスタマイズによってプライベートなプロパティやフィールドを作ることも可能です

*7:データ専用型とは基本的にメソッドを持たない型であり、パブリックでない変数というのは概ねメソッドから利用されるものであるためです

*8:特にデバッグ時などにその真価が発揮されるでしょう

*9:分解構文で利用されるメソッドです。分解構文は今後執筆するであろう「タプル」で詳しく説明するする予定です

*10:何度も繰り返される定形的なコードのこと