C#を勉強しなおしてみた(5)
Equalsについて

2024年12月15日

どもです。

本投稿は、以下の投稿の続きモノです。

今回は、「Equals」メソッドについて書いていきます。

1. 「Equals」とは

MSDNの解説によれば、Equalsは、

2つのオブジェクトインスタンスが等しいかどうかを判断する

と解説されています。この1文だけで完結されても、ピンときませんが、MSDNのページをよく読むと、Object.Equals の補足 API 解説というページに、詳細が記載されていました。

MSDNのページをよく読むと、Object.Equals の補足 API 解説によれば、Equalsメソッドの「オブジェクトインスタンスが等しい」とは、対象のインスタンスが「値型」か「参照型」かにより、比較の仕方が異なるようです。

1.1. 参照型のEquals

比較対象のインスタンスが「参照型」の場合、Equalsメソッドでは「参照の等価性」を確認します。即ち、「メモリ上の同じ値を参照しているか否か」が判定されます。このことを、以下のコードで確認します。

using System;

Person person1a = new Person("John");
Person person1b = person1a;
Person person2 = new Person(person1a.ToString());

Console.WriteLine("Calling Equals:");
Console.WriteLine("person1a and person1b: {0}", person1a.Equals(person1b));
Console.WriteLine("person1a and person2: {0}", person1a.Equals(person2));

Console.WriteLine("\nCasting to an Object and calling Equals:");
Console.WriteLine("person1a and person1b: {0}", ((object)person1a).Equals((object)person1b));
Console.WriteLine("person1a and person2: {0}", ((object)person1a).Equals((object)person2));

public class Person
{
	private string personName;

	public Person(string name)
	{
		this.personName = name;
	}

	public override string ToString()
	{
		return this.personName;
	}
}

このコードを実行すると、下図のようになります。



このように、Person型のperson1aと、それを「代入した」(同じ参照を持つ)person1bをEqualsメソッドで比較すると、結果は「True」となり、「一致している」と判定されています。

一方で、同じPerson型のインスタンスであるperson2は、personNameフィールドに格納されている文字列はperson1aと同じです。しかしPersonオブジェクトは別途newしており、別の参照となります。そのため、person1aとperson2をEqualsメソッドで比較すると、結果は「False」となり「一致していない」と判定されます。

1.2. 値型のEquals

比較対象が「値型」の場合は、その名の通り「値が同じか」を判定します。例を見てみます。

int value1 = 12;
int value2 = 12;

Console.WriteLine("{0} ({1}) = {2} ({3}): {4}",
	value1, value1.GetType().Name,
	value2, value1.GetType().Name,
	value1.Equals(value2));

このコードを実行すると、以下のような結果になります。



このように、value1とvalue2は同じ値であるため、Equalsメソッドでは「一致している」と判定されます。

ところが、値型に関しては、値が同じであるにもかかわらずEqualsメソッドの結果が「False」、「一致していない」と判定される場合があります。それは、同じ値が格納されている、異なる型のオブジェクトを比較した場合です。具体的なコードは、以下の通りです。

short value1 = 12;
int value2 = 12;

Console.WriteLine("{0} ({1}) = {2} ({3}): {4}",
	value1, value1.GetType().Name,
	value2, value2.GetType().Name,
	value1.Equals(value2));

object obj1 = (object)value1;
object obj2 = (object)value2;

Console.WriteLine("{0} ({1}) = {2} ({3}): {4}",
	obj1, obj1.GetType().Name,
	obj2, obj2.GetType().Name,
	obj1.Equals(obj2));

このコードを実行すると、以下のような結果が得られます。



このように、コード上、value1とvalue2はともに「12」という値が格納されていますが、それぞれ「short」、「int」型のオブジェクトです。実行時のオブジェクトの型が異なる場合、実際の値が一致していても「不一致」と判定されます。

一方で、クラスと似ている値型オブジェクトとして、「構造体」があります。構造体は値型であるため、Equalsメソッドは「値の等価性」を確認します。そこで疑問になるのが、「値型」の構造体のメンバーが「参照型」であった場合です。これを確かめてみます。

コードは、以下のようになります。

using System;

Person person1 = new Person("John");
Person person2 = new Person("John");

Console.WriteLine("Calling Equals:");
Console.WriteLine("person1a and person2: {0}", person1.Equals(person2));

public struct Person
{
	private string personName;

	public Person(string name)
	{
		this.personName = name;
	}

	public override string ToString()
	{
		return this.personName;
	}
}

このコードを実行すると、以下のようになります。



このように、参照型であるstring型に同じ文字列が格納された2つの構造体オブジェクトをEqualsメソッドで比較した場合、「一致している」と判定されています。

2. まとめ

今回は、Equalsメソッドについて書きました。このメソッドは、比較対象オブジェクトが「参照型」か「値型」がにより比較する内容が異なります。対象オブジェクトが「参照型」の場合には、「参照先の等価性」(同じオブジェクトを参照しているか)を比較、確認します。それに対して「値型」の場合には、「値の等価性」を確認します。これは、単に「同じ値か否か」です。一方で、同じ値を格納している値型の変数でも、型が異なれば、Equalsメソッドは「不一致」と判定します。

値型である構造体に関しても、Equalsメソッドは使用できます。構造体の場合には、構成する各要素のEqualsメソッドの結果に依存します。そのため、2つの異なる構造体オブジェクトについて、構造体を構成する参照型の要素が同じオブジェクトを参照していれば、Equalsメソッドでの比較は「一致」と判定されます。

このように、比較を行うEqualsメソッドでも、対象が「対象型」か「参照型」かによって判定する内容が異なります。そのため、一致しているか否かの判定でEqualsを使用する際には、軽々とEqualsメソッドを使用するのではなく、確認対象が何かをよく考え、Equalsを使用することが適切か否かを判断してから使用する必要があります。そうでなければ期待する判定が行えず、予期せぬ不具合を作りこんでしまうので、注意が必要です。

この記事が誰かの助けになれば幸いです。ではっ!