C# 6.0 くらいの頃から脈々とずっとテーマに挙がっている「データ」関連の機能で、2つほど提案が挙がっています。

新しいものが出たというよりは、プライマリ コンストラクターとかレコード型とか言われていたものを、コンパクトに分割した感じのものです。

長らく先延ばしになっていた機能ですが、C# 8.0 でいよいよ実装しようといことで、 詳細を詰めた結果2つに分かれたという感じだと思われます。

data クラス/構造体

1つ目は、data クラス/構造体 と言われるもので、

  • class/struct の前に data 修飾子を付ける
  • public なフィールド、自動プロパティから、以下のものを自動生成
    • GetHashCode
    • Equals
    • ==, != 演算子
    • ToString

というようなもの。

印象としては、匿名型の延長で、ちゃんとしたクラス・構造体に昇格させたいとい時に使うものな感じです。

using System;

// class に data 修飾子を付ける
data class Point
{
    public int X { get; }
    public int Y { get; }
}

class Program
{
    static void Main()
    {
        // 匿名型
        var p1 = new { X = 1, Y = 2 };

        // data クラス
        var p2 = new Point { X = 1, Y = 2 };

        // 比較とかが自動的に作られる
        Console.WriteLine(p2 == new Point { X = 1, Y = 2 });
    }
}

対象となるフィールド/プロパティ

基本的には、比較やハッシュ値計算に使われるのは public なフィールドと自動プロパティだけです。 private なものや、自動実装でないものは除外されます。 (「データから計算で得られる値を、1回だけ計算してキャッシュしておきたい」みたいなとき、そのキャッシュを比較・ハッシュ値計算に使うことはあまりないので。)

ただ、自動実装でないプロパティでも、DataMember属性を付ければ、比較・ハッシュ値計算の対象にできます。

immutable データに対してオブジェクト初期化子

また、data クラス/構造体では、immutable なデータ(get-only なプロパティ)に対してもオブジェクト初期化子が使えます。 (これまでの C# だと、匿名型で特別扱いで認められてた。通常のクラスだと、オブジェクト初期化子が使えるのは書き換え可能なフィールド/プロパティだけ。)

例えば上記の例では、X, Y の2つのプロパティは get-only ですが、new Point { X = 1, Y = 2 } という書き方が許されます。 これを認めるために、get-only プロパティを、実際には以下のようにコード生成する予定だそうです。

class Point
{
    // <> から始まる名前は、通常の C# コードでは書けない。
    // 通常は使えない名前を使うことで、C# コードからは読み書きさせない。
    // (コンパイラー生成のコードからだけ読み書きする。)
    private int <>X;
    public int X => <>X;

    private int <>Y;
    public int Y => <>Y;

    // 以下、Equals や GetHashCode なども生成
}

class Program
{
    static void Main()
    {
        // コンパイラーはオブジェクト初期化子を以下のように展開
        var p2 = new Point();
        p2.<>X = 1;
        p2.<>Y = 2;
    }
}

名前付きタプル

一方で、タプルの延長で、ちゃんとしたクラス・構造体に昇格させるみたいな構文も追加。名前付きタプルと呼ぶそうです。

以下のように、クラス名に続けてタプルみたいなものを書くことで、タプルに名前が付きます。

using System;

// 型名の後ろにタプル的なものを書く
class Point(int X, int Y);

class Program
{
    static void Main()
    {
        // タプル
        var p1 = (X: 1, Y: 2);

        // 名前付きタプル
        var p2 = new Point(1, 2);
        Console.WriteLine(p2.X);
        Console.WriteLine(p2.Y);
    }
}

見ての通り、コンストラクターとプロパティが生成されます。 また、タプルと同様、比較、ハッシュ値計算や、Deconstruct メソッドなども生成されるそうです。

この例だと class Point(int X, int Y); だけ書きましたが、クラスの中身も持てるそうです。 (昔あったレコード型の提案に結構近い。)

また、class Point(int, int); と言うように、メンバー名は省略できます。 この場合、タプルと同様、Item1, Item2 というような番号付きのメンバーが生成されます。