原文リンク:https://www.infoworld.com/article/3607372/how-to-work-with-record-types-in-csharp-9.html
原文タイトル:How to work with record types in C# 9
翻訳:砂漠の果ての狼(Google翻訳による補強)
C# 9 の record 型を活用して、不変型とスレッドセーフなオブジェクトを構築する。
不変性により、オブジェクトはスレッドセーフになり、メモリ管理の改善にも役立ちます。また、コードの可読性と保守性が向上します。不変オブジェクトは、作成後に変更できないオブジェクトとして定義されます。したがって、不変オブジェクトは本質的にスレッドセーフであり、競合状態の影響を受けません。
つい最近まで、C# は標準では不変性をサポートしていませんでした。C# 9 では、新しい init-only プロパティと record 型によって不変性のサポートが導入されました。init-only プロパティはオブジェクトの個々のプロパティを不変にするために使用でき、record はオブジェクト全体を不変にするために使用できます。
不変オブジェクトは状態を変更しないため、マルチスレッドやデータ転送オブジェクトなど、多くのユースケースにおいて不変性は理想的な特性です。この記事では、C# 9 で init-only プロパティと record 型を使用する方法について説明します。
この記事のコード例を使用するには、システムに Visual Studio 2019 がインストールされている必要があります。まだインストールされていない場合は、こちらから Visual Studio 2019 をダウンロードできます。
Visual Studio でコンソールアプリケーションプロジェクトを作成する
まず、Visual Studio で .NET Core コンソールアプリケーションプロジェクトを作成しましょう。システムに Visual Studio 2019 がインストールされていることを前提として、以下に概説する手順に従って、Visual Studio で新しい .NET Core コンソールアプリケーションプロジェクトを作成します。
- Visual Studio IDE を起動します。
- 「新しいプロジェクトの作成」をクリックします。
- 「新しいプロジェクトの作成」ウィンドウで、表示されたテンプレートの一覧から「コンソールアプリ (.NET Core)」を選択します。
- 「次へ」をクリックします。
- 次に表示される「新しいプロジェクトの構成」ウィンドウで、新しいプロジェクトの名前と場所を指定します。
- 「作成」をクリックします。
これらの手順に従うと、Visual Studio 2019 に新しい .NET Core コンソールアプリケーションプロジェクトが作成されます。このプロジェクトは、この記事の後半で使用します。
C# 9 での init-only プロパティの使用
init-only プロパティは、オブジェクトの初期化時にのみ値を割り当てることができるプロパティです。次のコードは、init-only プロパティを含むクラスを示しています。
public class DbMetadata
{
public string DbName { get; init; }
public string DbType { get; init; }
}
次のコードスニペットを使用して、DbMetadata クラスのインスタンスを作成し、そのプロパティを初期化できます。
DbMetadata dbMetadata = new DbMetadata()
{
DbName = "Test",
DbType = "Oracle"
};
init-only フィールドへの後続の代入は無効であることに注意してください。そのため、次のステートメントはコンパイルできません。
dbMetadata.DbType = "SQL Server";
C# 9 での record 型の使用
C# 9 の record 型は、読み取り専用プロパティのみを持つ軽量で不変のデータ型(または軽量クラス)です。record 型は不変であるためスレッドセーフであり、作成後に変更または変更することはできません。record 型はコンストラクタでのみ初期化できます。
record キーワードを使用して、次のコードスニペットのように record を宣言できます。
public record Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string Country { get; set; }
}
型を単に record としてマークするだけでは(前のコードスニペットのように)、それ自体では不変性が提供されないことに注意してください。record 型に不変性を提供するには、次のコードスニペットのように init プロパティを使用する必要があります。
public record Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
public string Address { get; init; }
public string City { get; init; }
public string Country { get; init; }
}
次のコードスニペットを使用して、Person クラスのインスタンスを作成し、そのプロパティを初期化できます。
var person = new Person
{
FirstName = "Joydip",
LastName = "Kanjilal",
Address = "192/79 Stafford Hills",
City = "Hyderabad",
Country = "India"
};
C# 9 での with 式の使用
一部のプロパティが同じ値を持つ場合、別のオブジェクトからオブジェクトを作成したいことがよくあります。しかし、record 型の init-only プロパティはこれを防ぎます。たとえば、次のコードスニペットは、Person という名前の record 型のすべてのプロパティがデフォルトで init-only であるため、コンパイルできません。
var newPerson = person;
newPerson.Address = "112 Stafford Hills";
newPerson.City = "Bangalore";
幸いなことに、回避策があります——with キーワードです。プロパティ値の変更を指定することで、with キーワードを利用して別の record 型からインスタンスを作成できます。次のコードスニペットは、これを実現する方法を示しています。
var newPerson = person with
{ Address = "112 Stafford Hills", City = "Bangalore" };
C# 9 での record 型の継承
record 型は継承をサポートしています。つまり、既存の record 型から新しい record 型を作成し、新しいプロパティを追加できます。次のコードスニペットは、既存の record 型を拡張して新しい record 型を作成する方法を示しています。
public record Employee : Person
{
public int Id { get; init; }
public double Salary { get; init; }
}
C# 9 での位置引数による record
デフォルトでは、位置パラメータを使用して作成された record 型のインスタンスは不変です。つまり、次のコードスニペットのように、コンストラクタパラメータを使用して順序付けられた引数リストを渡すことで、record 型の不変インスタンスを作成できます。
var person = new Person("Joydip", "Kanjilal", "192/79 Stafford Hills", "Hyderabad", "India");
C# 9 での record インスタンスの等価性のチェック
C# でクラスの 2 つのインスタンスが等しいかどうかをチェックする場合、比較はこれらのオブジェクトの参照(同一性)に基づきます。ただし、record 型の 2 つのインスタンスが等しいかどうかをチェックする場合、比較は record 型のインスタンス内の値に基づきます。
次のコードスニペットは、2 つの文字列プロパティで構成される DbMetadata という名前の record 型を示しています。
public record DbMetadata
{
public string DbName { get; init; }
public string DbType { get; init; }
}
次のコードスニペットは、DbMetadata record 型の 2 つのインスタンスを作成する方法を示しています。
DbMetadata dbMetadata1 = new DbMetadata()
{
DbName = "Test",
DbType = "Oracle"
};
DbMetadata dbMetadata2 = new DbMetadata()
{
DbName = "Test",
DbType = "SQL Server"
};
Equals メソッドを使用して等価性を確認できます。次の 2 つのステートメントは、コンソールウィンドウに "false" を表示します。
Console.WriteLine(dbMetadata1.Equals(dbMetadata2));
Console.WriteLine(dbMetadata2.Equals(dbMetadata1));
DbMetadata record 型の 3 番目のインスタンスを作成する次のコードスニペットを考えます。インスタンス dbMetadata1 と dbMetadata3 は同じ値を含んでいることに注意してください。
DbMetadata dbMetadata3 = new DbMetadata()
{
DbName = "Test",
DbType = "Oracle"
};
次の 2 つのステートメントは、コンソールウィンドウに "true" を表示します。
Console.WriteLine(dbMetadata1.Equals(dbMetadata3));
Console.WriteLine(dbMetadata3.Equals(dbMetadata1));
record 型は参照型ですが、C# 9 は値ベースの等価セマンティクスに従うための合成メソッドを提供します。コンパイラは、値ベースのセマンティクスを強制するために、record 型に対して以下のメソッドを生成します。
Object.Equals(Object)メソッドのオーバーロードrecord型をパラメータとして受け取る仮想のEqualsメソッドObject.GetHashCode()メソッドのオーバーロード- 2 つの等値演算子のメソッド、すなわち
==演算子と!=演算子 record型がSystem.IEquatable<T>を実装
さらに、record 型は Object.ToString() メソッドのオーバーロードを提供します。これらのメソッドは暗黙的に生成され、再実装する必要はありません。
C# の Equals メソッドの確認
Equals メソッドが暗黙的に生成されているかどうかを確認できます。そのためには、次のように DbMetadata record に Equals メソッドを追加します。
public record DbMetadata
{
public string DbName { get; init; }
public string DbType { get; init; }
public override bool Equals(object obj) =>
obj is DbMetadata dbMetadata && Equals(dbMetadata);
}
コードをコンパイルすると、コンパイラは次のメッセージでエラーをマークします。
型 'DbMetadata' は、同じパラメーター型を持つ 'Equals' というメンバーを既に定義しています
record 型はクラスですが、record キーワードは、record をクラスと区別する追加の値型のような動作とセマンティクスを提供します。record 自体は参照型ですが、独自の組み込みの等価性チェックを使用します。等価性は参照ではなく値によってチェックされます。最後に、record は可変にすることもできますが、主に不変性のために設計されていることに注意してください。