1
0

値型レコードを逆コンパイルしてみる

Last updated at Posted at 2024-09-09

はじめに

C# 9.0 でレコード型が、C# 10.0 で値型レコードが導入されました。今回は値型レコードを逆コンパイルしてみて、どのような実装になっているか見ていこうと思います。

なお参照型レコードは継承が絡むため、値型レコードよりやや複雑です。

追記:ハッシュコードのマジックナンバー -1521134295 について
解説しているブログによると、素数とかをあれして衝突しにくいハッシュコードにしているようです。

逆コンパイル結果

record struct Person(string FirstName, string LastName, int Age);

[NullableContext(1)]
[Nullable(0)]
internal struct Person : IEquatable<Person>
{
	public Person(string FirstName, string LastName, int Age)
	{
		this.FirstName = FirstName;
		this.LastName = LastName;
		this.Age = Age;
	}

	// 書き換え可能なプロパティです
	// readonly get になっている点が細かい
	public string FirstName { readonly get; set; }
	public string LastName { readonly get; set; }
	public int Age { readonly get; set; }

	[NullableContext(0)]
	[CompilerGenerated]
	public override readonly string ToString()
	{
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.Append("Person");
		stringBuilder.Append(" { ");
		if (this.PrintMembers(stringBuilder))
		{
			stringBuilder.Append(' ');
		}
		stringBuilder.Append('}');
		return stringBuilder.ToString();
	}

	// このメソッドはデバッグ用?
	[NullableContext(0)]
	[CompilerGenerated]
	private readonly bool PrintMembers(StringBuilder builder)
	{
		builder.Append("FirstName = ");
		builder.Append(this.FirstName);
		builder.Append(", LastName = ");
		builder.Append(this.LastName);
		builder.Append(", Age = ");
		builder.Append(this.Age.ToString());
		return true;
	}

	[CompilerGenerated]
	public static bool operator !=(Person left, Person right)
	{
		// !left.Equals(right) のほうが
		// メソッド呼び出しを1回減らせるような気がするものの
		// こっちのほうが意図が明確です
		return !(left == right);
	}

	[CompilerGenerated]
	public static bool operator ==(Person left, Person right)
	{
		return left.Equals(right);
	}

	[CompilerGenerated]
	public override readonly int GetHashCode()
	{
		// バッキングフィールドに直接アクセスしている!
		// マジックナンバー -1521134295
		return (EqualityComparer<string>.Default.GetHashCode(this.<FirstName>k__BackingField) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(this.<LastName>k__BackingField)) * -1521134295 + EqualityComparer<int>.Default.GetHashCode(this.<Age>k__BackingField);
	}

	[NullableContext(0)]
	[CompilerGenerated]
	public override readonly bool Equals(object obj)
	{
		// obj is Person value && this.Equals(value); は
		// is とキャストを1回で済ませてお得な気がするものの
		// JIT 的には同じなのかも
		return obj is Person && this.Equals((Person)obj);
	}

	[CompilerGenerated]
	public readonly bool Equals(Person other)
	{
		return EqualityComparer<string>.Default.Equals(this.<FirstName>k__BackingField, other.<FirstName>k__BackingField) && EqualityComparer<string>.Default.Equals(this.<LastName>k__BackingField, other.<LastName>k__BackingField) && EqualityComparer<int>.Default.Equals(this.<Age>k__BackingField, other.<Age>k__BackingField);
	}

	[CompilerGenerated]
	public readonly void Deconstruct(out string FirstName, out string LastName, out int Age)
	{
		FirstName = this.FirstName;
		LastName = this.LastName;
		Age = this.Age;
	}
}

かゆいところ

  • readonly 修飾
  • バッキングフィールドに直接アクセス
    • 自動実装プロパティのバッキングフィールドに直接アクセスしているところがあります
    • プロパティ経由の場所もあり、統一されていないようです
    • 実行時プロパティ参照はだいたいインライン化されるため、パフォーマンスの違いはあまりなさそうです
  • マジックナンバー -1521134295
    • ハッシュコードの計算にマジックナンバーを使っています
    • -1521134295 は検索してもよくわかりませんでした
    • 統計的にハッシュコードが衝突しにくくなるんですかね?
    • 別のプロパティのハッシュコード計算にも同じマジックナンバーを使っています
    • 16 進数: FFFF FFFF A555 5529
    • ビット: 1111111111111111111111111111111110100101010101010101010100101001
    • 排他的論理和とビットシフトに近いことをやっていそう?
    • ビットシフトと違って桁が溢れても情報が失われないとか、そういうのかもしれません

値型タプルとの違い

値型レコードの実装は、基本的なところは値型タプルの実装と似ている気がします。

異なる点

  • IEquatable<T> 以外のインターフェイスの継承
    • 値型タプルは並び替えをサポートするようです
  • 値をフィールドで公開するかプロパティで公開するか
    • 値型タプルはフィールドで公開しています
    • フィールドを参照渡しできるか(ref out)で違いが出ます
  • GetHashCode() の実装
    • 値型タプルはハッシュコードの計算に HashCode.Combine() を使用しています
  • == != 演算子のサポート
    • 値型タプルではサポートされていません

おわりに

Equals() 等のボイラープレートコードの実装を、レコード型ならコンパイラに任せることができます。短いコードで表現しつつ多機能の恩恵を受けられるのがいいです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0