ICompareble<T>와 IComparer<T>르 이용하여 객체의 선후 관계를 정의하라

 

컬렉션을 원하는 조건으로 정렬하는 방법은 다양하다. 그중 C#은 ICompareble<T>와 IComparer<T> 인터페이스를 지원하고 이는 MS에서 최적화 해준 Sort()기능을 사용할 수 있다.

 

public class Customer : IComparable<Customer>, IComparable
{
        private readonly string strName;
        public Customer(string strName)
        {
            this.strName = strName;
        }

        public int CompareTo(Customer other) 
            => strName.CompareTo(other);

        int IComparable.CompareTo(object other)
        {
            if (!(other is Customer))
                throw new ArgumentException("Argument is not Customer", nameof(other));

            Customer otherCurtomer = other as Customer;

            return this.CompareTo(otherCurtomer);
        }
}

Customer Class의 strName 문자열 기준으로 정렬기능이 있는 코드 이다.

구지 IComparable을 쓸필요가 있을까 싶지만 .NET Framework 2.0 이전에 개발된 기능을 사용하고 싶다면 필요하다.

여기서 눈여겨 봐야하는 부분은 

int IComparable.CompareTo(object other)
{
            if (!(other is Customer))
                throw new ArgumentException("Argument is not Customer", nameof(other));

            Customer otherCurtomer = other as Customer;

            return this.CompareTo(otherCurtomer);
}

이 부분이다. 이 부분 덕분에 

Customer c1 = new Customer("Health Man");
Employee e1 = new Employee();

if(c1.CompareTo(e1) == 0)
	Console.WriteLine("Is Equal!");

아래와같은 이상한 동작을 피할 수 있다.

이유는 IComparable.CompareTo라는 강력한 타입으로 직접 지정을 했기 때문이다. 따라서 사용자는 구지 사용하고 싶다면 

Customer c1 = new Customer("Health Man");
Employee e1 = new Employee();

if(c1.CompareTo((Customer)e1) == 0)
	Console.WriteLine("Is Equal!");

과 같이 명시적으로 캐스팅을 진행해야 한다. 하지만 퍼포먼스에 신경을 쓰는 개발자 이거나 뭔가 이상한 낌새를 느끼는 개발자는 위와같은 코드를 피할 확률이 높아진다.

 

만약 IComparable.CompareTo를 CompareTo으로 코딩을 하면 어떻게 될까?

 

간단한 좌우를 판단하는 로직에서는 괜찮지만 평균적으로 nlog(n)의 시간복잡도를 가지고 있는 정렬 알고리즘은 nlog(n)만큼의 박싱과 언박싱이 이루어진다. 알다시피 박싱과 언박싱은 최소화 하는게 최선의 방법이라 했다.

 

그 다음은 Customer의 이름뿐만아니라 수요액에 따라 정렬하는 기능을 추가하고 한다.

 

우선 클래스의 기본적인 선후 관계의 연산자들을 정의한다.(테스트 결과 구지 안해도 된다)

public static bool operator <(Customer left, Customer right) => left.CompareTo(right) < 0;
public static bool operator >(Customer left, Customer right) => left.CompareTo(right) > 0;
public static bool operator <=(Customer left, Customer right) => left.CompareTo(right) <= 0;
public static bool operator >=(Customer left, Customer right) => left.CompareTo(right) >= 0;

 

우선 2가지 방법이 있다. 

1번은 .NET Framework에 제네릭 기능이 포함된 이후 개발된 API에 적용가능하다.

2번은 1번 이전에도 가능하다.

 

1번 Comparision 델리게이트를 이용한 방법

public static Comparison<Customer> CompareByMoney => (left, right) => left.dblMoney.CompareTo(right.dblMoney);

2번 IComparer 인터페이스로 추가적인 선후 관계 논리를 제공하는 경우를 위한 표준화된 방법

 private class MoneyComparer : IComparer<Customer>
{
            int IComparer<Customer>.Compare(Customer left, Customer right) => left.dblMoney.CompareTo(right.dblMoney);
}

private static Lazy<MoneyComparer> moneyCompare = 
	new Lazy<MoneyComparer>(()=>new MoneyComparer());

public static IComparer<Customer> MoneyCompare => moneyCompare.Value;

 

1,2번 코드 사용방법

List<Customer> customers = new List<Customer>();
customers.Add(new Customer("c", 25));
customers.Add(new Customer("b", 23));
customers.Add(new Customer("z", 200));
customers.Add(new Customer("d", 2));
customers.Add(new Customer("j", 2021));
customers.Add(new Customer("f", 1215));
customers.Add(new Customer("q", 12513));
customers.Add(new Customer("i", 4574));

//1번 : Comparsion 델리게이트 사용 방법
customers.Sort(Customer.CompareByMoney);

//2번 : Comparer 인터페이스 사용 방법
customers.Sort(Customer.MoneyCompare);

 

자 이렇게 ICompare, IComparer, Comparision을 이용하여 객체의 관계 및 컬렉션에서의 정렬방법을 포스팅했다.

 

결론. 

1. ICompare<T>는 평소와 같이 구현 ICompare는 이전버전을 강력한 타입 ICompare.CompareTo 으로 정의 및 구현

(박싱, 언박싱 최소화)

2. Comparision<T> 델리게이트를 이용하면 컬렉션 정렬 메서드를 제공하기 쉽다.

(MS에서 최적화 시킨 Sort기능 사용)

 

'Program > Effective C#' 카테고리의 다른 글

Effective C# ITEM21  (0) 2020.11.26
Effective C# ITEM 19  (0) 2020.11.18
Effective C# ITEM 18  (1) 2020.11.15
Effective C# ITEM 17  (0) 2020.11.08
Effective C# ITEM16  (0) 2020.11.05

+ Recent posts