[C#] 8.2.5 IComparable 인터페이스와 IComparer 인터페이스

IComarable 인터페이스와 IComparer 인터페이스는 개체의 값 비교를 제공하기 위해 정의되었습니다. C#의 컬렉션은 대부분 Sort 메서드를 제공하는데 IComparable 인터페이스 기반의 요소를 보관하고 있을 때 정상적으로 동작하고 그렇지 않으면 예외를 발생합니다. 그리고 IComparer 개체를 입력 인자로 받는 Sort 메서드가 중복 정의되어 있는데 정렬하는 과정에서 입력 인자로 받은 IComparer 개체를 이용합니다.

또한, C#의 System에 정의되어 있는 기본 형식들은 ICompable 인터페이스를 기반으로 정의되어 있어서 기본 형식을 보관한 컬렉션은 Sort 메서드를 이용하여 정렬할 수 있습니다.

1차원 배열은 기반 클래스인 Array 추상 클래스의 정적 메서드인 Sort를 이용하여 정렬할 수 있습니다. Array 추상 클래스의 Sort는 1차원 배열을 입력 인자로 받습니다.

▶ 기본 형식을 요소로 하는 1차원 배열의 정렬

static void Main(string[] args)
{
    int[] arr = new int[10] { 20, 10, 5, 9, 11, 23, 8, 7, 6, 13 };

    Array.Sort(arr); //정적 메서드 Sort를 이용, 입력 인자는 1차원 배열
    foreach (int i in arr)
    {
        Console.Write("{0} ",i);
    }
    Console.WriteLine();
}

▶ 실행 결과

5 6 7 8 9 10 11 13 20 23

그리고 대부분의 컬렉션에는 Sort 메서드를 개체의 멤버로도 제공하고 있습니다.

▶ 기본 형식을 요소로 하는 ArrayList의 정렬

static void Main(string[] args)
{
    ArrayList ar = new ArrayList();
    ar.Add(4);
    ar.Add(8);
    ar.Add(2);

    ar.Sort(); //개체의 멤버 메서드 Sort를 이용

    foreach (int i in ar)
    {
        Console.Write("{0} ", i);
    }
    Console.WriteLine();
}

▶ 실행 결과

2 4 8

물론, 사용자가 형식을 정의할 때 IComparable 인터페이스 기반으로 정의하면 이러한 장점을 활용할 수 있습니다. ICompable 인터페이스에는 자신과 입력 인자를 비교하여 결과를 반환하는 CompareTo 메서드를 약속하고 있습니다. 입력 인자 형식을 object 형식으로 되어 있으므로 프로그램 목적에 맞게 예외 처리 등을 이용하여 적절히 처리해야 합니다.

▶ IComparbla에서 약속한 멤버

interface IComparable
{
    int CompareTo(object obj);
}

▶ 사용자 정의 형식 개체를 요소로 하는 컬렉션 정렬

class Member:IComparable
{
    string name;
    string addr;
    public Member(string name, string addr)
    {
        this.name = name;
        this.addr = addr;
    }

    public int CompareTo(object obj) //IComparable에서 약속한 메서드 구현
    {
        Member member = obj as Member;
        if (member == null)
        {
            throw new ApplicationException("Member 개체가 아닙니다.");
        }
        return name.CompareTo(member.name);
    }
    public override string ToString()
    {
        return string.Format("이름:{0} 주소:{1}",name,addr);
    }
}
class Program
{
    static void Main(string[] args)
    {
        Member[] members = new Member[3];
        members[0] = new Member("홍길동", "율도국");
        members[1] = new Member("강감찬", "대한민국");
        members[2] = new Member("장언휴", "이에이치");

        Array.Sort(members);

        foreach (Member member in members)
        {
            Console.WriteLine(member);
        }
    }
}

▶ 실행 결과

이름:강감찬 주소:대한민국
이름:장언휴 주소:이에이치
이름:홍길동 주소:율도국

프로그램에서 하나 이상의 정렬 기능을 제공할 때는 어떻게 할까요? 기본이 되는 값으로 비교하는 것은 IComparable 인터페이스에서 약속한 CompareTo에서 정의하면 되겠죠. 그리고 다른 값으로 비교를 원한다면 IComparer 인터페이스 기반의 형식을 정의하세요. Sort 메서드는 IComparer 개체를 입력 인자로 받는 메서드도 지원하고 있습니다.

▶ IComparer에서 약속한 멤버

interface IComparer
{
    int Compare(object x, object y);
}

IComparer 인터페이스에는 두 개의 개체를 입력 인자로 받는 Compare 메서드를 약속하고 있습니다. 결국 비교를 담당하는 부분을 별도의 형식으로 정의하는 것입니다.

▶ 회원 개체 이름과 주소로 정렬

class AddrComparer : IComparer
{
    public int Compare(object x, object y) // IComparer에서 약속한 기능 구현
    {
        Member mx = x as Member;
        Member my = y as Member;

        if ((mx == null) || (my == null)) 
        {
            throw new ApplicationException("Member 개체가 아닌 인자가 있습니다.");
        }

        return mx.Addr.CompareTo(my.Addr);
    }
}
class Member : IComparable
{
    string name;
    public string Addr
    {
        get;
        private set;
    }

    public Member(string name, string addr)
    {
        this.name = name;
        this.Addr = addr;
    }

    public int CompareTo(object obj) //IComparable에서 약속한 기능 구현
    {
        Member member = obj as Member;
        if (member == null)
        {
            throw new ApplicationException("입력 인자가 Member 개체가 아닙니다. ");
        }
        return name.CompareTo(member.name);
    }

    public override string ToString()
    {
        return string.Format("이름:{0} 주소:{1}", name, Addr);
    }
}

class Program
{
    static void Main(string[] args)
    {
        Member[] members = new Member[3];
        members[0] = new Member("홍길동", "율도국");
        members[1] = new Member("강감찬", "대한민국");
        members[2] = new Member("장언휴", "이에이치");
        Array.Sort(members); //요소의 CompareTo 메서드 이용하여 정렬
        foreach (Member member in members)
        {
            Console.WriteLine(member);
        }
        Array.Sort(members, new AddrComparer()); //IComparer 개체를 이용하여 정렬
        Console.WriteLine("-------------------------");
        foreach (Member member in members)
        {
            Console.WriteLine(member);
        }
    }
}

▶ 실행 결과

이름:강감찬 주소:대한민국
이름:장언휴 주소:이에이치
이름:홍길동 주소:율도국
-----------------------------
이름:강감찬 주소:대한민국
이름:홍길동 주소:율도국
이름:장언휴 주소:이에이치