int
, float
등과 같은 primitive type은 기본적으로 비교 연산자가 지원 된다. 하지만 사용자가 정의한 타입에 대해서 비교 연산을 하고 싶다면 어떻게 해야 할까? 이에 대해서는 클래스에 자체적인 비교 메소드를 넣어주거나 할 수 있다.
class Example
{
public static int Compare(Example left, Example right)
{
//Do compare...
}
}
하지만 단순히 비교 연산 메소드를 구현하는 것만으로는 표준 라이브러리에 비교 연산자를 넘겨줘야 하는 경우엔 사용 할 수가 없다. 그럴 때 사용하는 것이 IComparer
, IComparable
인터페이스다.
2개의 각각 인터페이스는 서로 같아보이지만, 사용에 있어서 차이가 존재한다. 각 인터페이스의 차이에 대해서는 예제를 보면서 천천히 살펴보도록 하자.
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
와 IComparable
의 차이는 무엇일까? IComparable
은 인터페이스를 상속받은 클래스가 비교 연산을 할 수 있게 해주며, IComparer
는 특정 클래스에 대한 비교 연산 행위자를 구현 할 때에 사용한다.
먼저, IComparer
의 코드부터 살펴보도록하자.
public interface IComparer
{
int Compare(Object x, Object y);
}
마찬가지로 정수 값을 반환하여 대소 여부를 따진다. x
가 y
값 보다 작을 경우엔 음수 값을 반환하며, 동일 할 경우엔 0을 반환한다. 그리고 x
가 y
값 보다 더 크다면 양수 값을 반환한다. 이를 통해서 오름차순, 내림차순의 정렬 여부를 결정 할 수 있다.
그렇다면 어떻게 활용 할 수 있을까? 다시 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);
위의 예시를 실행하고 나면 역순으로 정렬되어 출력되는 것을 볼 수 있다.
본 포스팅에서 IComparable
과 IComparer
를 살펴보았다. 어쩌면 굳이 2개의 인터페이스로 나누어서 비교 가능함과 비교 연산행위를 분리한 것이 불필요하다고 느낄 수 있다. 비교 행위에 예외를 두어서 별도의 비교 연산자를 구현하는 것이 흔한 케이스는 아니다. 하지만 전혀 없는 일도 아니다.
string
을 예시로 살펴보도록하자. string
자체는 비교가 가능한 타입이다. 하지만 자신이 원하는 기존이 기존의 문자열 비교 알고리즘과 다르다면 IComparer
를 이용하여 직접 구현하여야 한다. 이처럼 표준 라이브러리 혹은 서드 파티 라이브러리에서 제공하는 클래스의 비교 연산과 사용자의 필요와 다를 경우 IComparer
를 요긴하게 사용 할 수가 있다.