[C#] IComparable and IComparer

LeeTaeHwa·2023년 3월 19일
0

C#

목록 보기
4/4

Introduction


int, float 등과 같은 primitive type은 기본적으로 비교 연산자가 지원 된다. 하지만 사용자가 정의한 타입에 대해서 비교 연산을 하고 싶다면 어떻게 해야 할까? 이에 대해서는 클래스에 자체적인 비교 메소드를 넣어주거나 할 수 있다.

class Example
{
	public static int Compare(Example left, Example right)
    {
    	//Do compare...
	}
}

하지만 단순히 비교 연산 메소드를 구현하는 것만으로는 표준 라이브러리에 비교 연산자를 넘겨줘야 하는 경우엔 사용 할 수가 없다. 그럴 때 사용하는 것이 IComparer, IComparable 인터페이스다.

2개의 각각 인터페이스는 서로 같아보이지만, 사용에 있어서 차이가 존재한다. 각 인터페이스의 차이에 대해서는 예제를 보면서 천천히 살펴보도록 하자.

IComparable


IComparable의 소스 코드는 다음과 같다.


public interface IComparable
{
    int CompareTo(object? obj);
}

반환값이 정수로 되어 있는데, 비교 결과를 음수, 양수, 0으로 나누어서 반환한다. 비교 대상이 되는 파라미터 값과 비교해봤을 때 더 작을 경우 음수를 반환하며, 거꾸로 더 클 경우엔 양수를 반환한다. 서로 같을 경우에는 0을 반환한다.

그러면 예제 코드를 보면서 어떻게 구현하는지 살펴보자.

public struct Vector2 : IComparable<Vector2>
{
    public float x;
    public float y;

    public float Magnitude => MathF.Sqrt(x * x + y * y);

    public Vector2(float x, float y)
    {
        this.x = x;
        this.y = y;
    }

    public override string ToString() => string.Format("x : {0}, y : {0}", x, y);

    public int CompareTo(Vector2 other) => Magnitude.CompareTo(other.Magnitude);
}

Vector2 를 이용하여 비교 연산자를 구현해보았다. 그리고 다음 코드를 살펴보자.

var vectors = new List<Vector2> 
{ 
	new Vector2(0.1f, 0.1f), 
    new Vector2(5f, 3f), 
    new Vector2(2f, 1.5f) 
};

foreach (var vector in vectors) Console.WriteLine(vector);

vectors.Sort();

foreach (var vector in vectors) Console.WriteLine(vector);

그리고 빌드를 한 후에 결과값을 출력 해보면 정렬이 되었음을 알 수가 있다.

IComparer


그렇다면 IComparerIComparable 의 차이는 무엇일까? IComparable 은 인터페이스를 상속받은 클래스가 비교 연산을 할 수 있게 해주며, IComparer 는 특정 클래스에 대한 비교 연산 행위자를 구현 할 때에 사용한다.

먼저, IComparer의 코드부터 살펴보도록하자.

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

마찬가지로 정수 값을 반환하여 대소 여부를 따진다. xy 값 보다 작을 경우엔 음수 값을 반환하며, 동일 할 경우엔 0을 반환한다. 그리고 xy 값 보다 더 크다면 양수 값을 반환한다. 이를 통해서 오름차순, 내림차순의 정렬 여부를 결정 할 수 있다.

그렇다면 어떻게 활용 할 수 있을까? 다시 Vector2 를 통한 사용 예를 살펴보도록 하자.

public class Vector2Comparer : IComparer<Vector2>
{
    public int Compare(Vector2 x, Vector2 y) => x.Magnitude.CompareTo(y.Magnitude) * -1;
}

위의 코드에서 기존의 비교값에 -1 을 곱하여 역순으로 정렬이 되게끔 하였다. 그리고 사용예시는 다음과 같다.

var vectors = new List<Vector2> 
{ 
	new Vector2(0.1f, 0.1f), 
    new Vector2(5f, 3f), 
    new Vector2(2f, 1.5f) 
};

foreach (var vector in vectors) Console.WriteLine(vector);

vectors.Sort(new Vector2Comparer());

foreach (var vector in vectors) Console.WriteLine(vector);

위의 예시를 실행하고 나면 역순으로 정렬되어 출력되는 것을 볼 수 있다.

Conclusion


본 포스팅에서 IComparableIComparer 를 살펴보았다. 어쩌면 굳이 2개의 인터페이스로 나누어서 비교 가능함과 비교 연산행위를 분리한 것이 불필요하다고 느낄 수 있다. 비교 행위에 예외를 두어서 별도의 비교 연산자를 구현하는 것이 흔한 케이스는 아니다. 하지만 전혀 없는 일도 아니다.

string 을 예시로 살펴보도록하자. string 자체는 비교가 가능한 타입이다. 하지만 자신이 원하는 기존이 기존의 문자열 비교 알고리즘과 다르다면 IComparer 를 이용하여 직접 구현하여야 한다. 이처럼 표준 라이브러리 혹은 서드 파티 라이브러리에서 제공하는 클래스의 비교 연산과 사용자의 필요와 다를 경우 IComparer 를 요긴하게 사용 할 수가 있다.

profile
하늘을 향해 걸어가고 있습니다.

0개의 댓글