C# IComparable, IComparer 인터페이스, CompareTo(), Compare()
March 12, 2022, 11:59 p.m.
이번 포스트에서는 IComparable, IComparer 인터페이스에 대해서 알아보도록 하겠습니다.
IComparable 인터페이스는 해당 클래스를 비교가 가능하게 하여 해당 컬렉션 객체의 정렬을 가능하게 합니다.
대표적인 컬렉션인 List는 Sort()라는 메서드가 있는데요, 해당 리스트 안에있는 객체들을 정렬하여 재배치하게 됩니다. 이때 리스트 안에 있는 객체들을 서로 비교할 수 있어야 정렬을 할 수 있겠죠? 그렇기 때문에 Sort() 메서드를 실행하면 내부적으로 해당 리스트 안에 있는 객체의 IComparable 인터페이스의 함수, CompareTo(T)를 실행하게 됩니다.
이 CompareTo(T)는 비교할 다른 객체 T를 인자로 받아 해당 객체가 비교할 객체보다 크면 양수를, 비교할 객체가 더 크면 음수를 반환합니다. 이를 토대로 List는 정렬을 진행하게 되는 것이죠.
즉 List에 객체를 넣을 것이고 정렬을 필요로 한다면 해당 객체는 ICompareable 인터페이스를 상속받고 CompareTo()를 선언해주어야 하는 것입니다.
IComparer 인터페이스는 살짝 다릅니다. 이름으로 알 수 있듯이, 이 객체는 정렬하는 방법만 정의하는 객체입니다. 어떤 객체를 다양한 방법으로 정렬하고 싶을 때 각각의 Comparer를 만들어 List.Sort(Comparer) 같은 방식으로 정렬하는 것입니다. 뒤에서 알아보겠습니다.
1. IComparable
먼저 IComparable 인터페이스를 아래의 예시를 보며 자세히 알아볼까요? 직육면체를 의미하는 Cubic 클래스를 만들어 보겠습니다.
public class Cubic : IComparable<Cubic>
{
private int W;
private int L;
private int H;
public Cubic(int w, int l, int h)
{
W = w;
L = l;
H = h;
}
public int volume()
{
return W * L * H;
}
public int CompareTo(Cubic ctc)
{
int v = volume()
int vToCompare = ctc.volume();
if (v > vToCompare)
return 1;
else if (v < vToCompare)
return -1;
else
return 0;
}
}
Cubic이라는 class를 만들었고 생성자를 통해 Cubic의 가로, 세로, 높이를 집어넣어 주었습니다. IComparable
직육면체는 부피를 토대로 정렬을 하는것이 편하겠죠? 그렇기 때문에 객체와 비교할 객체의 부피를 각각 구해주고 비교를 통해 양수, 음수, 혹은 0을 반환해 주었습니다.
이제 리스트를 생성하고 Cubic 객체를 넣어 보겠습니다.
List<Cubic> l = new List<Cubic>();
l.Add(new Cubic(10,10,10));
l.Add(new Cubic(7,11,9));
l.Add(new Cubic(9, 7, 2));
l.Add(new Cubic(12, 3, 3));
l.Add(new Cubic(2, 3, 1));
foreach(Cubic c in l)
{
Console.WriteLine (c.W+" "+c.L+" "+c.H+"-"+c.volume());
}
10 10 10-1000
11 11 11-1331
9 7 6-378
15 3 3-135
2 3 1-6
리스트에 순서대로 들어가 있는 모습을 볼 수 있네요. 부피도 나오는 모습을 볼 수 있습니다. 이제 정렬을 해보겠습니다.
l.Sort();
foreach(Cubic c in l)
{
Console.WriteLine (c.W+" "+c.L+" "+c.H+"-"+c.volume());
}
2 3 1-6
12 3 3-108
9 7 2-126
7 11 9-693
10 10 10-1000
부피를 토대로 정렬이 된 모습을 볼 수 있습니다. 즉, ComapreTo()를 통해 정의한 대로 정렬이 되었네요.
2. IComparer
두번째로 IComparer에 대해서 알아보겠습니다. 위의 Cubic 객체를 부피로 정렬을 할 때도 있고, 밑면의 면적만 가지고 정렬을 하고싶을 때도 있고, 높이만 가지고 정렬을 하고 싶을 때도 있다면 어떻게 해야 할까요? Cubic 객체를 따로 따로 만들어서 CompareTo() 메서드를 선언해야 할까요?
여기에 IComparer 인터페이스 라는 좋은 방법이 있습니다. 객체의 비교 방법을 객체 클래스 내부에서 정의하는 것이 아니라 Comparer를 따로 만들어 정렬을 할때 적용하는 것입니다. 아래의 예시를 통해 알아보겠습니다.
public enum Criteria {
byVolume,
byHeight,
byArea,
}
public class CubicComparer : IComparer<Cubic>
{
public Criteria c = Criteria.byVolume;
public int Compare(Cubic a, Cubic b)
{
if (c==Criteria.byVolume)
{
if (a.volume() > b.volume())
return 1;
else if(a.volume() < b.volume())
return -1;
else
return 0;
}
else if (c==Criteria.byHeight)
{
if (a.H > b.H)
return 1;
else if(a.H < b.H)
return -1;
else
return 0;
}
else
{
if (a.W * a.L > b.W * b.L)
return 1;
else if (a.W * a.L < b.W * b.L)
return -1;
else
return 0;
}
}
}
IComparer를 상속받은 CubicComparer 클래스를 만들었습니다. IComparable 클래스와 다르게 여기서는 Compare()메서드를 선언했습니다. enum을 만들어 해당 비교 방법에 따라 다르게 비교하도록 선언했습니다.
아래와 같이 사용할 수 있습니다!!
CubicComparer compare = new CubicComparer();
compare.c = Criteria.byVolume;
l.Sort(compare);
foreach(Cubic c in l)
{
Console.WriteLine (c.W+" "+c.L+" "+c.H+"-"+c.volume());
}
compare.c = Criteria.byHeight;
l.Sort(compare);
foreach(Cubic c in l)
{
Console.WriteLine (c.W+" "+c.L+" "+c.H+"-"+c.H);
}
compare.c = Criteria.byArea;
l.Sort(compare);
foreach(Cubic c in l)
{
Console.WriteLine (c.W+" "+c.L+" "+c.H+"-"+c.W * c.H);
}
2 3 1-6
12 3 3-108
9 7 2-126
7 11 9-693
10 10 10-1000
2 3 1-1
9 7 2-2
12 3 3-3
7 11 9-9
10 10 10-10
2 3 1-2
12 3 3-36
9 7 2-18
7 11 9-63
10 10 10-100
각각의 방법대로 정렬이 된 모습을 볼 수 있네요!!
이번 포스트에서는 IComparable, IComparer 인터페이스에 대해서 알아보았습니다.
IComparable IComparer