본문 바로가기
언어/C#

비교-정렬을 위한 두 인터페이스, IComparable과 IComparer

by alpacadabra 2022. 10. 2.

IComparable IComparer 는 비슷한 일을 하지만 근본적인 차이가 있다.

이 게시글에서는 두 인터페이스의 용도와 차이에 대해 알아볼 것이다.

 


IComparable

C#의 커스텀 클래스는 기본적으로 "비교 불가능"이다.

객체의 수많은 정보들 중에서 무엇을 기준으로 해야 할지 모르기 때문이다.

 


public class Person
{
    public string Name { get; }
    public int Age { get; }

    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
}

 

class Program
{
    static void Main()
    {
        Person jack = new Person("Jack", 20);
        Person john = new Person("John", 24);
        Person tom = new Person("Tom", 17);

        Person[] people = { jack, john, tom };
        Array.Sort(people);

        foreach (Person p in people)
        {
            Console.WriteLine(p.Name);
        }
    }
}

 

정렬은 비교의 연속, 따라서 비교가 불가하면 정렬도 불가하다


 

객체를 "비교 가능"하게 만들기 위해서는 IComparable 인터페이스를 상속받아 그 클래스를 comparable, 즉 비교 가능한 클래스로 만들어야 한다.

또한 인터페이스에서 요구하는 메서드인 CompareTo 메서드를 구현하여 객체들 간의 선후 관계를 정의해야 한다.

 


public class Person : IComparable<Person>
{
    public string Name { get; }
    public int Age { get; }

    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }

    int IComparable<Person>.CompareTo(Person other)
    {
        return Age - other.Age;
    }
}

 

나이에 대하여 오름차순으로 정렬되었다


 

CompareTo 의 규칙은 매우 간단하다.

음수를 반환한다면 other보다 순서가 빠른 것이고, 양수를 반환한다면 순서가 느린 것이다.

0은 순서가 같음을 의미한다. (다만 C#의 Sort는 unstable하므로 원래 순서가 유지된다는 보장은 없다)

구현이 귀찮다면 기본 자료형의 CompareTo 를 활용하는 방법도 있다.

 


int IComparable<Person>.CompareTo(Person other)
{
    return other.Name.CompareTo(Name);
}

 

이름에 대하여 사전순의 역으로 정렬되었다


 

C#의 기본 자료형은 이미 자신만의 CompareTo 를 가지고 있다.

그렇기에 아무런 사전 작업 없이 Sort가 가능한 것이다.

string의 경우 비교가 조금 번거롭기 때문에 String.CompareTo 를 잘 이용하면 사전순과 그 역으로 쉽게 정렬할 수 있다.

 


IComparer

IComparable 이 1인칭이었다면 IComparer 는 3인칭이라고 할 수 있다.

제 3의 객체에게 비교를 위탁하는 것인데, IComparable 보다 번거로운 대신 활용 범위가 넓다.

 


class Program
{
    static void Main()
    {
        int[] nums = { 3, 2, 7, 4, 6, 1, 8, 9, 5 };
        Array.Sort(nums);

        foreach (int num in nums)
        {
            Console.WriteLine(num);
        }
    }
}

 

수열을 오름차순이 아닌 내림차순으로 정렬하고 싶다


 

수열을 내림차순으로 정렬하고 싶지만 정수형의 CompareTo 메서드를 수정할 방법이 없는 상황이다.

이럴 때는 IComparer 를 통해 새로운 선후 관계를 정의하면 된다.

IComparable 과 마찬가지로, IComparer 를 상속받았다면 반드시 Compare 메서드를 구현해야 한다.

Compare 메서드는 CompareTo 와 논리 구조가 동일한데, 둘 다 반환값의 부호를 통해 객체의 선후 관계를 결정한다.

그러나 Compare 메서드는 3인칭이고 CompareTo 는 1인칭이라는 차이가 있다.

 


public class IntComparer : IComparer<int>
{
    int IComparer<int>.Compare(int x, int y)
    {
        return y - x;
    }
}

 

class Program
{
    static void Main()
    {
        int[] nums = { 3, 2, 7, 4, 6, 1, 8, 9, 5 };
        Array.Sort(nums, new IntComparer());

        foreach (int num in nums)
        {
            Console.WriteLine(num);
        }
    }
}

 

내림차순으로 정렬되었다


 

새로운 비교 메서드인 Compare 를 통해 정렬된 모습이다.

기존의 CompareTo 는 무시되기 때문에 CompareTo 를 수정할 권한이 없을 때는 물론이고 CompareTo 가 아예 존재하지 않을 때도 사용할 수 있다.

단, 객체를 정렬할 때 반드시 IComparer 를 상속받은 객체를 인자로 전달해주어야 한다.

그렇지 않으면 정렬 메서드는 Compare 의 존재를 알 수 없다.

'언어 > C#' 카테고리의 다른 글

Delegate와 Action, Func, Predicate  (0) 2023.07.20
IEnumerable과 foreach문의 관계  (0) 2023.07.19
C#의 동작 원리  (0) 2023.02.03
C#에서 배열을 부분 참조하는 방법  (0) 2022.12.29

댓글