ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 닷넷 클래스 object is 연산자 GetHashCode()
    .NET/C# Basic 2008. 10. 16. 00:42
    반응형

    닷넷 클래스 object

    object도 일종의 클래스 이므로 내부에 맴버를 가진다
    생성자는 아무것도 하지않는 디폴트생성자만 정의되어있으면 필드, 상수, 프로퍼티 인덱서 이벤트등은 가지지않는다.루트 클래스의 맴버는 모든 후손에게 상속되므로 메모리를 소모하는 멤버는 포항하기 어렵다 만약 object에 40바이트의 필드가 있다면 닷넷의 모든 객체는 최소 40바이트 이상으로 되어야하므로 메모리 낭비가 너무 심해질것이다 그래서 객체를 관리하는 일반적인 메서드만을 가진 object 로부터 파생된 모든클래스의 객체들은 이메서드를 가지는 셈이다

     ToString  public  virtual  객체를 문자열 형태로 표현한다 티폴트로 클래스 이름을 리턴하는 데 필요시 재정의할수있다
     GetType  public  객체의 타입정보를 제공하는 System,Type객체를 리턴한다
     MemberwiseClone  protected  객체의 맴버끼리 일대일복사한다 얕은 복사이다
     GetHashCode  public virtual  객체를 검색하기 위한 해시값을 리턴한다
     Equals  public vitual
     public static
    두객체를 비교하여 같은 객체인지 판별한다 null객체와 비교할수 있는 정적버번도 중복정의되어있다
     ReferenceEquals  public static  객체가 같은 대상을 가리키고 있는 비교한다
     Finalize 또는 ~Object  protect vitual  정리 작업을 수행하는 파괴자이다 Finalize라는 이름을 쓰는 대신 클래스명 앞에 ~를 붙인다



    속성에  virtual 이 포함된 가상메서드들은 파생클래스에서 필요에 따라 재정의 할수 있으며 재정의 하라고 만들어놓은것이다

    특히
    객체를 문자열로 변환하는 ToString  메서드가 자주재정의된다


    using System;

    class Time
    {
     public int hour, min, sec;
     public Time(int h, int m, int s)
     {
      this.hour = h;
      this.min = m;
      this.sec = s;
     }

    // public override string ToString()
    // {
    //  return hour+"시"+ min+"분"+ sec+"초";
    // }


     public void OutTime()
     {
      Console.WriteLine("현재시간{0}시{1}분{2}", hour,min,sec);
     }

     
    }

    class CStest
    {
     static void Main()
     {
      Time Now = new Time(18,25,55);
      Now.OutTime();
      Console.WriteLine(Now);
     }
    }

    ////////////////////객체의 비교

    object 클래스에는 객체의동일성을 비교하는 세개의 메서드가 포함되어있다
    닷넷 라이브러리는 내부적으로 객체 비교가 필요할 때 이 메서드들을 호출하여 두 객체가 같은지 아닌지 판단한다 이중 Equals 메서드는 재정의 가능하여 클래스에 따라 고유한 비교 방법을 정의 할 수 있다

    public  virutal bool Equals (object obj)

    가상 메서드로 선언되어 있으면 A.Equals(B) 형식으로 사용한다. A와 B가 같은 객체인지를 비교하는데 같으면 true , 다르면 false를 리턴한다 object의 디폴트 구현은 참조가 같은지 즉 두 객체가 가리키는 번지가 같은지를 비교하도록 되어있다. 파생클래스에서 참조가 아닌 실체 클래스의 내용을 비교하고 싶다면 이 메서드를 재정의한다

    public static bool Equals(object objA, object objB)

    정적 메서드로 선언되어 있으면 object.Equals (A,B) 형식으로 사용한다. 두객체를 참조로 비교한다는 것은 동일하지만 둘중하나가 null일 경우에도 비교할 수 있다는 점이 다르다 B가 null 인 경우는 가상 메서드로도 A.Equals(null)로 비교할수있지만 반대의 경우라면 null.Equals(B) 식으로 호출할 수 없다. null은 실제 객체가 아니므로 메서드를 호출하지 못한다. 이런경우를 위해 정적메서드가 필요하며 object.Equals(null, B) 식으로 호출할수있다 만약 둘다 null 이면 이때는 true 를 리턴하고 둘중하나만 null 이면 false 를 리턴한다 둘중하나라도null이아니면 가상 Equals 를 호출하므로 가상 Equals를 재정의하면 이 메서드도 재정의되는 효과가 있다

    public static bool ReferenceEquals(object objA, object objB)

    정적 Equals 와 마찬가지로 참조 대상을 비교한다 즉 주소가 같은지만 평가하며 실체의 실제 내용을 비교하지 않는다 정적 Equals 와 기본동작은 같지만 재정의할 수 없고 평가 방법이 고정되어있다는 점이 다르다. Equals 의 재정의 여부와는 상관없이 참조를 비교를 하고 싶다면 이 메서드를 호출한다 정적 Equals 는 비교 객체중하나가null인 특수한 경우를 위해 존재하므로 실제로는 두개의 비교메서드가 있는 셈이다 이둘은 기본적으로 참조를 비교한다는  면에서 동일하지만 재정의를 할 수 이는가 없는가가 다르다 다음 예제는 Time 클래스의 객체들을 두메서드로 비교해 본다

    using System;

    class Time
    {
     private int hour, min, sec;
     public Time(int h , int m , int s)
     {
      this.hour = h;
      this.min = m;
      this.sec = s;
     }

     public  override bool Equals(object obj)     // Equals메서드가 없으면 A.A1은 false를 반환한다
     {
      Time Other = (Time)obj;
      return (Other.hour == hour && Other.min == min && Other.sec == sec);
     }

    class cstest
    {
     static void Main()
     {
      Time A = new Time(1,4,5);
      Time B = new Time(1,2,3);
      Time A1 = new Time(1,4,5);
      Time C = A;
      Console.WriteLine("Equals(A,B) = "+Equals(A,B));
      Console.WriteLine("Equals(A,C) = "+Equals(A,C));
      Console.WriteLine("Equals(A,A1) = " + Equals(A, A1)); 
      // Equals메서드가 없으면 A.A1은 false를 반환한다

      Console.WriteLine("ReferenceEquals(A,B) = " + ReferenceEquals(A, B));
      Console.WriteLine("ReferenceEquals(A,C) = " + ReferenceEquals(A, C));
     }

    }

    Time은 object로부터 상속받은 가상 Equals 메서드를 재정의하여 참조가 아닌 객체의 내용을 비교하도록 했다
    시분초 필드가 모두 일치하면 같은것이고 셋중하나라도틀리면 다른것이다 Main 에서는 A와 A1을 똑같은 시간으로 초기화했고 C에는 A를 대입하여 같은 대상을 참조하도록 초기화 했다 . 이상태에서 A,A1와 A,C를 각 메서드로 비교해 보았다.

    객체의 실제 내용을 비교하도록 Equals를 재정의 했으므로 A,C뿐만 아니라 A,A1 형도 동일한 것으로 판단한다 하지만 ReferenceEquals는 A,C는 같다고 평가하지만 A,A1는 다르다고 평가한다, 두 객체 모두 1시 4분 5초라는 똑같은 시간값을 가지고 있지만 힙상의 번지가 다르기때문에 틀린것으로 평가하는것이다   A, A1에 대한 비교결과가  이렇게 틀린 이유는 참조를 비교하느냐 값을 비교하느냐의 차이이다
    가상 Equals도 원래는 참조를 비교하지만 Time 에서 재정의 하여 내용을 비교하도록 했기 때문에 A,A1은 같다고 평가 한 것이다 위 예제에서 Time 클래스의 Equals 메서드 재정의문을 삭제해 버리면 Equals(A,A1) 도 false가 리턴된다 . Equals (A, A1)은 정적 메서드 호출문이지만 메서드의 내부에 A.Equals(A1) 또는 B.Equals(A) 를 호출하므로 재정의의 영향을 받는다

    객체를 비교할 때 비교 메서드 대신== 연산자를 사용할 수도 있는데 사용자 입장에선 == 연산자가 훨씬 더 직관적이고 쉽다. 아무래도 Equals (A,A1) 보다는 A==A1 이 더 보기에 좋다 == 연산자는 타입에 따라 비교하는 방법이 달라 지는데 값타입은 내용을 비교하며 참조 타입은 번지를 비교한다 물론 == 연산자도 오버로딩할 수 있으므로 클래스 타입에 대해 참조가 아닌 내용을 비교할 수 있다

    이때 == 연산자를 오버로딩했으면 Equals 메서드도 같이 재정의해야하며 그렇지 않으면 경고가 발생한다 반대의 경우도 마찬가지이며 Equals와 == 연산자는 항상 쌍으로 재정의해야한다 또 == 이 재정의 되면 !=도 당연히 같이 재정의되어야한다. 어떤 방법으로 비교를 하더라도 결과가 깥아야 하며 비교의 일관성이 확보되어야 하기 때문이다. 위 예제의 Time 클래스가 좀 더 완벽해지려면 다음 두연산자가필요하다

    /* public static bool operator ==(Time A, Time B)
     {
      return (A.hour == B.hour && A.min == B.min && A.sec == B.sec);
     }

     public static bool operator !=(Time A, Time B)
     {
      return !(A==B);
     }
    }*/


    그렇다면 객체 비교를 위한 수단으로 == 이라는 편리하고도 직관적인 방법이 있는데 왜 object는 굳이 Equals니 ReferenceEquals 니 하는 메서드를 == 과 함께 재정의하도록 해 놓았을까? 사실 == 만 오버로딩해도 객체를 내용으로  정확하게 비교할 수 있으므로 object가 굳이 비교메서드를 맴버로 포함해야할 마땅한 이유는 없으며 연산자만으로 비교하는것은 구문으로보나 구조적으로 보나 더 명코하고 간결하다

    비교 방법이 이렇게 다양하고 번거로운 이유연산자 오버로딩을 지원하지 못하는 언어가 있기 때문인데 좀더 구체적으로 얘기하자면 비주얼 베이직과 호환성을 확보하기 위해서이다. 닷넷 공통언어 규약(CLS)에 연산자 오버로딩이 의무사항이 아니므로 닷넷 라이브러리는 == 연산자를 활용하지 못하며 그래서 루트 클래스가 비교 메서드를 가질수 밖에 없다. C#만을 쓴다면 Equals메서드는 전혀 필요치 않은 존재이며 닷넷의 언어 독립성 확보를 위한 비용이라고 볼수잇다
    위 예제를 컴파일하면 GetHasCode메서드도 재정의하는것이 좋다는 경고가 발생한다 이 메서드는 객체를 맵에 저장할 떄 저장 위치를 찾기 위한 해시 코드를 제공하는 비교 방법이 바뀌면 해시코드가 영향을 받으므로 같이 재정의하라는 권고이다, 맵에 저장할 계획이 없다면 일단은 무시해도 상관없는데 자세한 것은 맵에서 연구해 보자.



    is 연산자

    object 객체를 비교하기 전에 Point 형 객체인지 먼저 검사
    is 연산자를 이용해서 Point 형인지 검사하고, 맞는 경우만 값을 비교하도록 변환

    public override bool Equals( object o )
        {
            // Point 형 객체인가요?
            if ( !(o is Point) ) return false;

            return ( x == ((Point)o).x && y == ((Point)o).y );
        }


    GetHashCode 메서드는 객체를 나타낼 수 있는 Hash 코드를 리턴
    정수형 키 값으로, Hash 테이블을 구성할 때 사용

    Equals 메서드를 정의하는 경우 GetHashCode 메서드도 같이 정의
    클래스 멤버 중에서 하나만 존재하는 특이한 값이 있다면, 그 값을
    쓰면 되고, 없다면, 클래스 멤버를 조합해서 정수 값을 하나 생성

         public override int GetHashCode()
        {
            return x^y;  // HashCode 생성
        }






    반응형

    댓글

Designed by Tistory.