본문 바로가기
책/Effective C#

Effective C# - Item20 IComparable<T>와 IComparer<T>를 이용하여 객체의 선후 관계를 정의하라

by 코딩하는 돼징 2023. 12. 10.
반응형

객체의 값 비교(정렬 등)정의하는 인터페이스 두 가지

IComparable과 IComparer


1. IComparable

타입의 기본적인 선후 관계 정의

CompareTo(Object)

public int CompareTo (object? obj);

매개변수

obj : 비교할 대상 객체

반환

음수 : 현재 객체가 대상 객체보다 작은 경우

0 : 현재 객체가 대상 객체와 같은 경우

양수 : 현재 객체가 대상 객체보다 큰 경우


01 (제네릭 버전) IComparable

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

    public int CompareTo(Piggy other)
    {
        return this.Age.CompareTo(other.Age);
    }
}

02 (제네릭이 아닌)IComparable 

IComparable은 제네릭이 아닌 버전으로 object 타입을 사용하여 비교 메서드를 정의한다. 이 버전은 런타임에 타입 안정성을 보장할 수 없으며 박싱/언박싱을 통한 성능 저하가 발생할 수 있다.

public class Piggy : IComparable
{
    public string Name { get; set; }
    public int Age { get; set; }

    public int CompareTo(object obj)
    {
        if (!(obj is Piggy otherPiggy))
        {
            throw new ArgumentException("Object is not a Piggy");
        }
        Piggy otherPiggy = (Piggy)obj;
        return this.Age.CompareTo(otherPiggy.Age);
    }
}

왜 제너릭이 아닌 버젼이 필요할까?

하위 호완성 : .NET Framework 2.0이전에는 IComparable.CompareTo() 메서드는 System.Object 타입의 매개변수를 취하는 형태로 정의되어 있다. 이는 IComparable 인터페이스의 일반성(generics)이 도입되기 전에 정의된 메서드이기 때문이다. 따라서 CompareTo 메서드를 사용하려면 System.Object가 필요하다.

타입 : IComparable인터페이스에 정의된 CompareTo()메서드는 IComparable.CompareTo()와 같이 명시적인 방법으로 인터페이스 메서드를 구현했기 때문에 IComparable 타입의 참조를 통해서만 메서드를 호출할 수 있다. 그러므로 이를 직접 사용하지 않는 경우 접근조차 불가능하다. 그러므로 특정 타입을 직접 사용하지 않는 경우에도 접근 가능하도록 하기 위해서이다.


03 사용 예시

public class Example
{
    public static void Main()
    {
        Piggy piggy1 = new Piggy { Name = "PinkPig", Age = 30 };
        Piggy piggy2 = new Piggy { Name = "BluePig", Age = 25 };
        Piggy piggy3 = new Piggy { Name = "YellowPig", Age = 35 };

        Console.WriteLine($"{piggy1.Name} vs. {piggy2.Name}: {piggy1.CompareTo(piggy2)}");
        Console.WriteLine($"{piggy1.Name} vs. {piggy3.Name}: {piggy1.CompareTo(piggy3)}");
        Console.WriteLine($"{piggy2.Name} vs. {piggy3.Name}: {piggy2.CompareTo(piggy3)}");
    }
}

결과


04 관계연산자

기본적인 선후 관계는 IComparable을 통해 구현해야 한다. IComparable 구현할 때에는 관계연산자도 함께 오버로딩하여 일관된 결과를 제공해야한다.

public static bool operator <(Piggy piggy1, Piggy piggy2)
{
    return piggy1.CompareTo(piggy2) < 0;
}
public static bool operator >(Piggy piggy1, Piggy piggy2)
{
    return piggy1.CompareTo(piggy2) > 0;
}
public static bool operator <=(Piggy piggy1, Piggy piggy2)
{
    return piggy1.CompareTo(piggy2) <= 0;
}
public static bool operator >=(Piggy piggy1, Piggy piggy2)
{
    return piggy1.CompareTo(piggy2) >= 0;
}

public class Program
{
    public static void Main()
    {
        Piggy piggy1 = new Piggy { Name = "Piggy1", Age = 3};
        Piggy piggy2 = new Piggy { Name = "Piggy2", Age = 2};

        // IComparable을 구현한 클래스의 관계 연산자 사용
        if (piggy1 < piggy2)
        {
            Console.WriteLine($"{piggy1.Name}이 {piggy2.Name}보다 작다");
        }
        else
        {
            Console.WriteLine($"{piggy1.Name}이 {piggy2.Name}보다 크거나 같다");
        }
    }
}

2. IComparer<T>

기본적인 선후 관계 이외에 추가적은 선후 관계를 정의할 수 있다. 이를 통해 정렬이나 비교시에 원하는 비교 기준을 사용자가 정의할 수 있다.

01 먼저 나이를 비교하고 나이가 같으면 몸무게를 비교하여 추가적인 선후 관계를 정의

public class PiggyAgeAndWeight : IComparer<Piggy>
{
    public int Compare(Piggy x, Piggy y)
    {
        int ageComparison = x.Age.CompareTo(y.Age);
        if (ageComparison == 0)
        {
            return x.weight.CompareTo(y.weight);
        }
        return ageComparison;
    }
}

02 사용 예시

List<Piggy> piggies = new List<Piggy>
{
    new Piggy { Name = "Piggy1", Age = 3, weight = 100 },
    new Piggy { Name = "Piggy2", Age = 2, weight = 80 },
    new Piggy { Name = "Piggy3", Age = 5, weight = 120 },
    new Piggy { Name = "Piggy4", Age = 3, weight = 110 }
};

Console.WriteLine("Sorting 전:");
foreach (var piggy in piggies)
{
    Console.WriteLine($"Name: {piggy.Name}, Age: {piggy.Age}, Weight: {piggy.weight}");
}

piggies.Sort(new PiggyAgeAndWeight());
Console.WriteLine("Sorting 후:");
foreach (var piggy in piggies)
{
    Console.WriteLine($"Name: {piggy.Name}, Age: {piggy.Age}, Weight: {piggy.weight}");
}


결론

IComparable과 IComparer<T>는 객체의 값 비교(정렬 등)을 정의하는 인터페이스 IComparable은 타입의 기본적인 선후 관계를 정의하고, IComparer<T>는 추가적인 선후 관계를 정의할 수 있다.

 

 

 

본 게시글은 Effective C#을 읽고 정리하였습니다.

 

반응형

댓글