[태그:] <span>ReferenceEquals</span>

C#의 object는 .NET Framework의 Object 형식을 부르는 형식 이름입니다. 앞에서도 계속 얘기를 했던 것처럼 object 형식은 모든 형식의 기반이 되는 형식입니다. C#에서 사용자 정의 형식을 정의하면 묵시적으로 object 형식에서 파생됩니다. 또한, 개발자는 파생 관계를 문법적으로 표현할 필요도 없고 표현해서도 안 됩니다.

먼저, object 형식을 구성하는 멤버들을 살펴봅시다.

object 형식에서 생성자는 기본 생성자를 제공하고 있습니다. 기본 생성자란 입력 매개 변수가 없는 생성자를 말합니다.

public object()

object 형식에서 접근 지정이 public으로 지정된 멤버 메서드는 다음과 같습니다.

▶object 형식의 public으로 접근 지정된 멤버 메서드

public virtual bool Equals(object obj)
public static bool Equals (object obj1, object obj2)
public virtual int GetHashCode()
public Type GetType()
public static bool ReferenceEquals (object obj1,object obj2)
public virtual string ToString ()

C#에서 static 키워드가 명시된 멤버를 정적 멤버라고 합니다. 정적 멤버는 형식 명을 통해 접근이 가능한 멤버로 개체의 멤버가 아니라 형식의 멤버입니다. 그리고 virtual 키워드가 있는 메서드는 가상 메서드로 파생 형식에서 재정의할 수 있습니다. 재정의란 기반 형식에서 정의한 것을 무효화시키고 새롭게 정의하는 것을 말합니다. 이에 대한 자세한 사항은 6장 캡슐화와 7장 상속과 다형성에서 자세히 설명하겠습니다.

3.1.1 Equals, ReferenceEquals

Equals 메서드와 ReferenceEquals 메서드는 두 개의 개체가 같은지를 비교할 때 사용하는 메서드이며 정적 멤버와 개체의 멤버가 있습니다. 정적 멤버는 입력 매개변수로 전달된 두 개의 인자가 같은지를 판단하며 개체의 멤버는 입력 매개변수로 전달된 인자와 자신이 같은지를 판단합니다. 기본적으로는 참조된 개체가 같은지를 판단하지만 개체의 멤버 Equals는 가상 메서드이기 때문에 목적에 따라 값이 같은지를 판단하게 재정의가 가능합니다. 그리고 값 형식과 string 형식에서는 개체의 멤버 Equals를 값이 같은지를 판단하도록 재정의되어 있습니다. ReferenceEquals는 참조된 개체가 같은지를 판단합니다.

▶ 값 형식의 Equals, ReferenceEquals 메서드

static void Main(string[] args)
{
    int i = 2;
    int j = i;
    Console.WriteLine("i.Equals(j) => {0}", i.Equals(j));
    Console.WriteLine("object.Equals(i,j) => {0}",object.Equals(i, j));
    Console.WriteLine("object.ReferenceEquals(i,j) =>{0}", object.ReferenceEquals(i, j));
}

▶ 결과

i.Equala(j) => True
object.Equals(i,j) => True
object.ReferenceEquals(i,j) => False

위 같은 경우에 i와 j는 값이 같습니다. 값 형식은 개체의 멤버 Equals를 값이 같은지를 판단하도록 재정의가 되어 있어 i.Equals(j) 결과는 참이 됩니다. 정적 멤버 Equals는 내부적으로 입력 인자로 전달된 첫 번째 개체의 멤버 Equals 메서드를 호출하여 결과를 반환하므로 object.Equals(i,j) 결과도 참이 됩니다.정적 멤버 ReferenceEquals는 개체가 같은지를 판별하는데 값 형식은 변수가 독립적으로 할당되어 관리되므로 i와 j는 같을 수 없습니다. 이에 object.RefernceEquals(i,j) 결과는 거짓입니다.

string은 참조 형식이지만 값 형식처럼 개체의 멤버 Equals를 값이 같은지를 판단하도록 재정의되어 있어 값을 비교합니다. 하지만 서로 다른 개체일 경우 ReferenceEquals의 결과는 거짓이 됩니다.

▶ string 형식의 Equals와 ReferenceEquals

static void Main(string[] args)
{
    string s1 = "hello";
    string s2 = s1;
    string s3 = string.Format("hello");

    Console.WriteLine("s1.Equals(s2) => {0}", s1.Equals(s2));
    Console.WriteLine("s1.Equals(s3) => {0}", s1.Equals(s3));
    Console.WriteLine("object.ReferenceEquals(s1,s2) =>{0}",
        object.ReferenceEquals(s1, s2));
    Console.WriteLine("object.ReferenceEquals(s1,s3) =>{0}",
        object.ReferenceEquals(s1, s3));
 }

▶ 결과

s1.Equals(s2) => True
s2.Equals(s3) => True
object.ReferenceEquals(s1,s2) => True
object.ReferenceEquals(s1,s3) => False

위의 예에서 s1과 s2는 같은 개체를 참조합니다. 그리고 s3는 string.Format 메서드를 이용하여 새로운 개체를 생성하였습니다. s1과 s2는 Eqauls과 ReferenceEquals의 결과가 모두 참으로 나오지만 s1과 s3는 ReferenceEquals은 거짓이 나오는 것을 확인할 수 있습니다. 하지만 string의 Equals 메서드는 값이 같은지를 비교하여 결과를 반환하므로 s1과 s3의 Equals 결과는 참입니다.

C#에서는 string을 제외한 참조 형식의 Equals 메서드는 참조하는 개체가 같은지를 판단한다는 것에 주의하세요. 물론, 개발자가 재정의하여 목적에 맞게 변경할 수도 있습니다.

▶ 참조 형식의 Equals와 ReferenceEquals

class Example
{
    int val;
    public Example(int val)
    {
        this.val = val;
    }
}
class Program
{
    static void Main(string[] args)
    {
        Example e1 = new Example(1);
        Example e2 = new Example(1);
        Console.WriteLine("e1.Equals(e2) => {0}", e1.Equals(e2));
        Console.WriteLine("object.Equals(e1,e2) => {0}", object.Equals(e1, e2));
        Console.WriteLine("object.ReferenceEquals(e1,e2) =>{0}",
            object.ReferenceEquals(e1, e2));
        Example e3 = e1;
        Console.WriteLine("e1.Equals(e3) => {0}", e1.Equals(e3));
        Console.WriteLine("object.Equals(e1,e3) => {0}", object.Equals(e1, e3));
        Console.WriteLine("object.ReferenceEquals(e1,e3) =>{0}", 
            object.ReferenceEquals(e1, e3));
    }
}

▶ 결과

e1.Equals(e2) => False
object.Equals(e1,e2) => False
object.ReferenceEquals(e1,e2) =>False
e1.Equals(e3) => True
object.Equals(e1,e3) => True
object.ReferenceEquals(e1,e3) =>True

테스트를 위해 Example 클래스를 정의하였습니다. 그리고 테스트는 두 개의 변수가 같은 값을 갖는 다른 개체를 각각 참조할 때와 두 개의 변수가 같은 개체를 참조할 때에 대하여 테스트를 하였습니다. 참조 형식의 Equals 메서드는 같은 개체인지 비교하므로 세 가지 모두 결과가 같습니다. 두 개의 변수가 같은 값을 갖는 다른 개체를 각각 참조하는 경우에는 참조한 개체가 다르므로 값이 같아도 결과는 모두 거짓입니다. 그리고 두 개의 변수가 같은 개체를 참조할 때의 결과는 모두 참입니다.

이번에는 class Example을 struct Example로 변경하여 값 형식일 경우에 어떻게 되는지에 대해 얘기를 해 봅시다. 값 형식은 개체의 멤버 Equals가 값이 같으면 참을 반환므로 서로 다른 개체일 때도 결과는 참입니다. 더군다나 값 형식은 변수마다 독립적으로 메모리가 할당되고 관리되므로 ReferenceEquals의 결과는 모두 거짓입니다.

▶ 예제 코드의 Example 클래스를 Example 구조체로 변경했을 때 결과

e1.Equals(e2) => True
object.Equals(e1,e2) => True
object.ReferenceEquals(e1,e2) =>False
e1.Equals(e3) => True
object.Equals(e1,e3) => True
object.ReferenceEquals(e1,e3) =>False

3.1.2 GetHashCode

GetHashCode는 해시 테이블과 같은 자료구조를 이용하여 개체를 관리할 때 적합한 키를 반환하는 메서드입니다. 실제 반환되는 값이 어떻게 발생하는지 규칙성이 없고 유일성을 보장하지 않습니다. 확률적으로 같은 값을 반환할 경우가 적다는 이유로 고유한 키로 사용하지 마십시오. 물론, GetHashCode는 가상 메서드이기 때문에 목적에 맞게 재정의할 수 있습니다.

3.1.3 GetType

GetType 메서드는 형식에 대한 상세정보를 갖는 Type 개체를 반환합니다. Type 개체는 런 타임에 형식에 대한 상세정보가 필요할 때 사용되는데 여러분들은 이 책을 보시고 난 후에 동적으로 라이브러리를 사용하는 리플렉션에 대해 살펴보세요.

3.1.4 ToString

ToString 메서드는 자신에 대한 정보를 문자열로 반환하는 메서드입니다. 기본적으로 형식 이름을 반환합니다. 그리고 ToString은 재정의가 가능한 메서드이며 기본 형식들은 갖고 있는 값을 문자열로 변환하여 반환하고 있습니다. 여러분이 사용자 정의 형식을 만들 때 이를 재정의하여 주요한 키를 문자열로 변환하여 반환하면 많은 곳에서 쉽게 사용할 수 있습니다.

▶ ToString 메서드 재정의

namespace Example
{
    class Stu//Stu 클래스에는 ToString 메서드를 재정의하였음
    {
        string name;
        public Stu(string name)
        {
            this.name = name;
        }
        public override string ToString()//재정의
        {
            return name;
        }
    }

    class Book//Book 클래스에는 ToString 메서드를 재정의하지 않았음
    {
        string name;
        public Book(string name)
        {
            this.name = name;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            //Stu 클래스에는 ToString 메서드를 중복 정의하였음
            Stu stu = new Stu("홍길동");
            Console.WriteLine(stu.ToString());//명시적으로 ToString 메서드 사용
            Console.WriteLine(stu);

            //Book 클래스에는 ToString 메서드를 중복 정의하지 않았음
            Book book = new Book("IT 전문가로 가는 길 Escort C#");
            Console.WriteLine(book.ToString());//명시적으로 ToString 메서드 사용
            Console.WriteLine(book);
        }
    }
}

▶ 결과

홍길동
홍길동
Example.Book
Example.Book

Escort C#