Help us understand the problem. What is going on with this article?

[.NET] コードを見直したくなる「参照型」等価判定の思わぬ落とし穴(特殊編)

一般編に続き特殊編です。

インターンプール

文字列のリテラルは「インターンプール」というテーブルに保持され、同一値に同じ参照が使用されます。
String.Intern メソッドでそれらの参照を取り出すことができます。

同一値のリテラルは参照も一致します。

string literal = "CodeOne";

Assert.IsTrue(Object.ReferenceEquals(literal, "CodeOne"));

値は同一でも、リテラルと連結された文字列では参照が異なります。

string built = new StringBuilder().Append("Code").Append("One").ToString();

Assert.IsTrue(Object.Equals(literal, built));
Assert.IsFalse(Object.ReferenceEquals(literal, built));

インターンプールから取得すると、同一値のリテラルと参照が一致します。

Assert.IsTrue(Object.ReferenceEquals(literal, String.Intern(built)));

※Ngen.exe でアセンブリをコンパイルした場合など、インターンプールに格納されない場合もあります。
《参考》String.Intern メソッド | Microsoft Docs

匿名型(C#)

匿名型では、Equals, GetHashCode メソッドのオーバーライドがコンパイラによって生成されます。
プロパティの数や順序によっても比較結果が異なりますので注意が必要です。

インスタンスが同じなら参照比較は一致します。

var staffA = new { Id = 1, Name = "山田" };

Assert.IsTrue(staffA == staffA);
Assert.IsTrue(Object.ReferenceEquals(staffA, staffA));

プロパティの数と順序、値が同じでも、インスタンスが異なれば参照比較は一致しません。

var staffA = new { Id = 1, Name = "山田" };
var staffB = new { Id = 1, Name = "山田" };

Assert.IsFalse(staffA == staffB);
Assert.IsFalse(Object.ReferenceEquals(staffA, staffB));

プロパティの数と順序、値が同じ場合、Equals の結果は true になります。

Assert.IsTrue(staffA.Equals(staffB));
Assert.IsTrue(staffA.Equals((object)staffB));
Assert.IsTrue(Object.Equals(staffA, staffB));

プロパティの順序が異なる場合、Equals の結果は false になります。

var staffA = new { Id = 1, Name = "山田" };
var staffC = new { Name = "山田", Id = 1 };

Assert.IsFalse(staffA.Equals(staffC));

プロパティの数が異なる場合、Equals の結果は false になります。

var staffA = new { Id = 1, Name = "山田" };
var staffD = new { Id = 1, Name = "山田", Role = "開発者" };
var staffE = new { Id = 1, Name = "山田", Role = (string)null };

Assert.IsFalse(staffA.Equals(staffD));
Assert.IsFalse(staffA.Equals(staffE));

匿名型(VB.NET)

VB.NET の匿名型では、Key キーワードによって Equals 比較に使用するプロパティを指定できます。
プロパティの数や順序に Key キーワードが絡みますので、C# よりさらに複雑となります。

インスタンスが同じなら参照比較は一致します。

Dim staffA = New With { Key .Id = 1, Key .Name = "山田" }

Assert.IsTrue(staffA Is staffA)
Assert.IsTrue(Object.ReferenceEquals(staffA, staffA))

プロパティの数と順序、値が同じでも、インスタンスが異なれば参照比較は一致しません。

Dim staffA = New With { Key .Id = 1, Key .Name = "山田" }
Dim staffB = New With { Key .Id = 1, Key .Name = "山田" }

Assert.IsFalse(staffA Is staffB)
Assert.IsFalse(Object.ReferenceEquals(staffA, staffB))

プロパティの数と順序、値が同じですべて Key キーワード付きの場合、Equals の結果は True になります。

Assert.IsTrue(staffA.Equals(staffB))
Assert.IsTrue(staffA.Equals(DirectCast(staffB, Object)))
Assert.IsTrue(Object.Equals(staffA, staffB))

プロパティの順序が異なる場合、Equals の結果は False になります。

Dim staffA = New With { Key .Id = 1, Key .Name = "山田" }
Dim staffC = New With { Key .Name = "山田", Key .Id = 1 }

Assert.IsFalse(staffA.Equals(staffC))

プロパティの数が異なる場合、Equals の結果は False になります。

Dim staffA = New With { Key .Id = 1, Key .Name = "山田" }
Dim staffD = New With { Key .Id = 1, Key .Name = "山田", Key .Role = "開発者" }

Assert.IsFalse(staffA.Equals(staffD))

プロパティの数と順序、値が同じでも、一方に Key キーワードが漏れていると Equals の結果は False になります。

Dim staffA = New With { Key .Id = 1, Key .Name = "山田" }
Dim staffE = New With { Key .Id = 1, .Name = "山田" }

Assert.IsFalse(staffA.Equals(staffE))

プロパティの数は Key キーワードなしも含めて一致しないと Equals は True を返しません。

Dim staffA = New With { Key .Id = 1, Key .Name = "山田" }
Dim staffF = New With { Key .Id = 1, Key .Name = "山田", .Role = "開発者" }

Assert.IsFalse(staffA.Equals(staffF))

Key キーワードの付いていないプロパティは Equals 判定に使用されません。

Dim staffG = New With { Key .Id = 1, Key .Name = "山田", .Role = "開発者" }
Dim staffH = New With { Key .Id = 1, Key .Name = "山田", .Role = "営業担当者" }

Assert.IsTrue(staffG.Equals(staffH))

Key キーワード付きのプロパティが1つもない場合、Equals は参照比較となります。

Dim staffI = New With { .Id = 1, .Name = "山田" }
Dim staffJ = New With { .Id = 1, .Name = "山田" }

Assert.IsTrue(staffI.Equals(staffI))
Assert.IsFalse(staffI.Equals(staffJ))

[.NET] 値型 等価判定の思わぬ落とし穴(一般編)
[.NET] 値型 等価判定の思わぬ落とし穴(特殊編)
[.NET] 参照型 等価判定の思わぬ落とし穴(一般編)
[.NET] 参照型 等価判定の思わぬ落とし穴(特殊編)

CodeOne
【品質と生産性にこだわるシステム開発】 .NET(C#/VB.NET)専門・リモート開発歴10年。即日・1時間から頼める常駐しないエンジニア。確かな技術で開発チームを手堅くサポートします。
https://codeone.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした