【C#】recordのススメ
ダメージ計算があるゲームなどにおいて、
パラメータの値のみを格納しているデータクラスを
使用したい場面があったりします。
public class Parameter { public string name { get; init; } public int hp { get; init; } public int attack { get; init; } /// <summary> /// コンストラクタ /// </summary> /// <param name="name">名前</param> /// <param name="hp">体力</param> /// <param name="attack">攻撃力</param> public Parameter(string name, int hp, int attack) { this.name = name; this.hp = hp; this.attack = attack; } /// <summary> /// 比較 /// </summary> public override bool Equals(object other) { if (other == null || this.GetType() != other.GetType()) { return false; } Parameter otherPara = (Parameter)other; return this.name == otherPara.name && this.hp == otherPara.hp && this.attack == otherPara.attack; } }
こんな感じの単純にプロパティのみを持ったクラスを使いたい場合は、
record
という選択肢があります。
learn.microsoft.com
下準備
Unityでrecord
を使用するにあたり、Initアクセサがあると便利です。
Scriptを新規作成し、以下のソースコードを記述します。
using System.ComponentModel; namespace System.Runtime.CompilerServices { [EditorBrowsable(EditorBrowsableState.Never)] internal class IsExternalInit { } }
使い方
冒頭で記述したclass
を、recordを使用して最小で記述するとこうなります。
public record Parameter(string name, int hp, int attack);
なんと1行です。
record
名の横に記述した引数名がそのままget
プロパティとなります。
record
名の横に引数を記述し、{}
の中に関数を書くことも可能です。
ただ、一行で書くのであればローカル変数として使うか、
タプル型を使用した方が良いでしょう。
丁寧に書くとこんな感じです。
個人的にはこっちの方がスキ。
public record Parameter { public string name { get; init; } public int hp { get; init; } public int attack { get; init; } public ParameterRecord(string name, int hp, int attack) { this.name = name; this.hp = hp; this.attack = attack; } }
Equal(比較演算)
class
動作の違いはEqual(比較演算)です。
冒頭で上げたプロパティを持つ、class
とrecord
を作成します。
プロパティの内容はどちらも同じです。
それぞれ2つずつインスタンスを作成します。
プロパティの内容は同じにし、Equalで調べるとどうなるでしょうか?
using UnityEngine; /// <summary> 例 </summary> public class Example : MonoBehaviour { //----------------------------------------------------------------------- // 定数 //----------------------------------------------------------------------- // 固定値 private const string Name = "Human"; private const int Hp = 10; private const int Attack = 5; //----------------------------------------------------------------------- // Unityメゾット //----------------------------------------------------------------------- /// <summary> /// 初期化処理 /// </summary> private void Start() { DataClassEqualTest(); DataRecordEqualTest(); } //----------------------------------------------------------------------- // Unityメゾット //----------------------------------------------------------------------- /// <summary> /// クラス比較 /// </summary> private void DataClassEqualTest() { var para1 = new ParameterClass(Name, Hp, Attack); var para2 = new ParameterClass(Name, Hp, Attack); Debug.Log($"{nameof(ParameterClass)} IsEqual : {para1.Equals(para2)}"); } /// <summary> /// レコード比較 /// </summary> private void DataRecordEqualTest() { var para1= new ParameterRecord(Name,Hp,Attack); var para2= new ParameterRecord(Name,Hp,Attack); Debug.Log($"{nameof(ParameterRecord)} IsEqual : {para1.Equals(para2)}"); } }
結果は以下のようになります。
`classOのEqualは同一のインスタンスかどうかを調べます。
別々のインスタンスなので結果はfalseとなります。
一方、record
のEqualは変数の値で比較を行います。
同じ値を使用しているためtrueとなります。
with(一部分コピー)
また、元々あるrecord
の一部分のプロパティをコピーして
新規record
インスタンスが作成できる機能 with
を使用できます。
// var【コピー先】 = 【コピー元】 with { プロパティ名 = 値, ... } var original= new ParameterRecord(Name,Hp,Attack); var copy = original with { name = "copyHuman" };
参考サイト
・レコード - C# リファレンス | Microsoft Learn
・init キーワード - C# リファレンス | Microsoft Learn
・c# 9.0 - Predefined type 'System.Runtime.CompilerServices.IsExternalInit' is not defined or imported - Stack Overflow