C#

[C#]List 속성별로 정렬

DevStory 2021. 10. 3.

이번 포스팅에서는 C#에서 속성별로 List 객체를 정렬하는 방법을 설명합니다.

 

LINQ OrderBy() 메서드를 사용하여 정렬된 새로운 List를 생성하는 방법이 있으며, 기존 List를 정렬하는 List에 내장된 Sort() 메서드가 있습니다.

 

Sort() 메서드를 사용하여 List를 정렬해야 하는 경우 Comparison<T> 대리자 또는 IComparer<T> 및 IComparable<T> 구현을 사용하여 정렬할 수 있습니다.


포스팅에서 사용할 Student 클래스

다음 코드는 이번 포스팅에서 사용할 Student 클래스입니다.

public class Student
{
    public string Name { get; set; }
    public int Age { get; set; } = 0;
    public int Score { get; set; } = 0;
}

LINQ OrderBy() 메서드 사용

List의 정렬된 인스턴스를 생성하려면 LINQ의 OrderBy() 메서드를 사용할 수 있습니다.

 

원본 List의 값은 변경되지 않고 새로운 List가 생성됩니다.

 

다음은 Name 속성 별로 정렬한 예제입니다.

List<Student> studentList = new List<Student>() {
    new Student() { Name = "Bill",  Age = 38, Score = 60 },
    new Student() { Name = "Fred",  Age = 22, Score = 80 },
    new Student() { Name = "Tom",   Age = 30, Score = 90 },
    new Student() { Name = "Bill",  Age = 38, Score = 90 },
    new Student() { Name = "Bill",  Age = 29, Score = 65 },
    new Student() { Name = "Steve", Age = 20, Score = 80 },
    new Student() { Name = "Bill",  Age = 28, Score = 70 },
    new Student() { Name = "Bill",  Age = 38, Score = 40 },
    new Student() { Name = "Sam",   Age = 25, Score = 50 },
    new Student() { Name = "Bill",  Age = 30, Score = 70 }
};

List<Student> studentNameSortList = studentList.OrderBy(x => x.Name).ToList();

foreach(Student studentObj in studentNameSortList)
{
    Console.WriteLine($"Name : {studentObj.Name} / Age : {studentObj.Age} / Score : {studentObj.Score}");
}

실행 결과

 

내림차순으로 정렬하고 싶은 경우 OrderBy() 메서드 다음에 Reverse() 메서드를 사용하거나 OrderByDescending() 메서드를 사용합니다.

// OrderBy() 적용 후 Reverse() 호출
List<Student> studentNameSortList = studentList.OrderBy(x => x.Name).Reverse().ToList();

// OrderByDescending() 메서드 사용
List<Student> studentNameSortList = studentList.OrderByDescending(x => x.Name).ToList();

 

OrderBy() 메서드를 사용하여 특정 속성 별로 정렬 후 추가적으로 정렬을 해야하는 경우 ThenBy() 메서드를 호출합니다.

 

다음 코드는 Name 속성 별로 정렬 후 Age 속성 별로 정렬하는 코드입니다.

List<Student> studentList = new List<Student>() {
    new Student() { Name = "Bill",  Age = 38, Score = 60 },
    new Student() { Name = "Fred",  Age = 22, Score = 80 },
    new Student() { Name = "Tom",   Age = 30, Score = 90 },
    new Student() { Name = "Bill",  Age = 38, Score = 90 },
    new Student() { Name = "Bill",  Age = 29, Score = 65 },
    new Student() { Name = "Steve", Age = 20, Score = 80 },
    new Student() { Name = "Bill",  Age = 28, Score = 70 },
    new Student() { Name = "Bill",  Age = 38, Score = 40 },
    new Student() { Name = "Sam",   Age = 25, Score = 50 },
    new Student() { Name = "Bill",  Age = 30, Score = 70 }
};

List<Student> studentNameSortList = studentList.OrderBy(x => x.Name).ThenBy(x => x.Age).ToList();

foreach(Student studentObj in studentNameSortList)
{
    Console.WriteLine($"Name : {studentObj.Name} / Age : {studentObj.Age} / Score : {studentObj.Score}");
}

 

실행 결과

 

실행 결과를 확인해보니 Name이 "Bill"이고 Age가 38인 개체가 3개 존재하는데, Scroe 속성은 정렬되지 않았습니다.

 

마지막으로 Score 속성도 정렬하기 위해 ThenBy() 메서드 호출 후 ThenBy() 메서드를 호출합니다.

List<Student> studentNameSortList = studentList.OrderBy(x => x.Name)
                                               .ThenBy(x => x.Age)
                                               .ThenBy(x => x.Score)
                                               .ToList();

위 코드처럼 ThenBy() 메서드 연속해서 호출하여 여러 개의 속성을 순서대로 정렬할 수 있습니다.

 

그리고 ThenBy() 메서드는 OrderBy() 메서드와 마찬가지로 오름차순으로 정렬되는데, 내림차순으로 정렬해야 하는 경우 ThenByDescending() 메서드를 호출합니다.

List<Student> studentNameSortList = studentList.OrderBy(x => x.Name).ThenByDescending(x => x.Age).ToList();
반응형

List<T>.Sort() 메서드 1. Comparison<T> 대리자 사용

비교 대리자를 사용하여 List에 정렬을 설정할 수 있습니다.

 

비교 대리자는 람다식을 사용하여 구현할 수 있습니다.

 

다음 코드는 Name 속성 별로 정렬한 코드입니다.

List<Student> studentList = new List<Student>() {
    new Student() { Name = "Bill",  Age = 38, Score = 60 },
    new Student() { Name = "Fred",  Age = 22, Score = 80 },
    new Student() { Name = "Tom",   Age = 30, Score = 90 },
    new Student() { Name = "Bill",  Age = 38, Score = 90 },
    new Student() { Name = "Bill",  Age = 29, Score = 65 },
    new Student() { Name = "Steve", Age = 20, Score = 80 },
    new Student() { Name = "Bill",  Age = 28, Score = 70 },
    new Student() { Name = "Bill",  Age = 38, Score = 40 },
    new Student() { Name = "Sam",   Age = 25, Score = 50 },
    new Student() { Name = "Bill",  Age = 30, Score = 70 }
};

studentList.Sort((x, y) => {
    return x.Name.CompareTo(y.Name);
});

foreach (Student studentObj in studentList)
{
    Console.WriteLine($"Name : {studentObj.Name} / Age : {studentObj.Age} / Score : {studentObj.Score}");
}

실행 결과

 

다음은 Name 속성 별로 정렬 후 Age 속성 별로 정렬하는 코드입니다.

studentList.Sort((x, y) => {
    int ret = String.Compare(x.Name, y.Name);
    return ret != 0 ? ret : x.Age.CompareTo(y.Age);
});

 

다음은 Name → Age → Score 순서대로 정렬하는 코드입니다.

studentList.Sort((x, y) => {
    int ret1 = String.Compare(x.Name, y.Name);
    int ret2 = ret1 != 0 ? ret1 : x.Age.CompareTo(y.Age);
    return ret2 != 0 ? ret2 : x.Score.CompareTo(y.Score);
});

List<T>.Sort() 메서드 2. IComparer<T> 구현

또 다른 방법으로는 IComparer 인터페이스를 상속받는 클래스에 Compare() 메서드를 재정의합니다.

 

그리고 Sort() 메서드에 해당 클래스의 인스턴스를 전달합니다.

 

비교할 필드가 여러 개인 경우 선호하는 방법입니다.

public class StudentComparer : IComparer<Student>
{
    public int Compare(Student x, Student y)
    {
        // 두 개체의 참조값 비교
        if (object.ReferenceEquals(x, y))
        {
            return 0;
        }

        // null이면 비교할 필요 없으므로 -1 반환
        if (x == null)
        {
            return -1;
        }
        
        // null이면 비교할 필요 없으므로 -1 반환
        if (y == null)
        {
            return 1;
        }

        // Name 속성부터 비교
        int ret1 = String.Compare(x.Name, y.Name);
        
        // Name 속성 비교 후 Age 속성 비교
        int ret2 = ret1 != 0 ? ret1 : x.Age.CompareTo(y.Age);
        
        // Age 속성 비교 후 Score 속성 비교
        return ret2 != 0 ? ret2 : x.Score.CompareTo(y.Score);
    }
}

public class Student
{
    public string Name { get; set; }
    public int Age { get; set; } = 0;
    public int Score { get; set; } = 0;
}

class Program
{
    static void Main(string[] args)
    {
        List<Student> studentList = new List<Student>() {
            new Student() { Name = "Bill",  Age = 38, Score = 60 },
            new Student() { Name = "Fred",  Age = 22, Score = 80 },
            new Student() { Name = "Tom",   Age = 30, Score = 90 },
            new Student() { Name = "Bill",  Age = 38, Score = 90 },
            new Student() { Name = "Bill",  Age = 29, Score = 65 },
            new Student() { Name = "Steve", Age = 20, Score = 80 },
            new Student() { Name = "Bill",  Age = 28, Score = 70 },
            new Student() { Name = "Bill",  Age = 38, Score = 40 },
            new Student() { Name = "Sam",   Age = 25, Score = 50 },
            new Student() { Name = "Bill",  Age = 30, Score = 70 }
        };

        studentList.Sort(new StudentComparer());

        foreach (Student studentObj in studentList)
        {
            Console.WriteLine($"Name : {studentObj.Name} / Age : {studentObj.Age} / Score : {studentObj.Score}");
        }
    }
}

실행 결과

Comparison<T> 대리자를 사용하는 방법보다 코드가 길지만, 코드가 정교하므로 에러가 발생할 가능성이 적습니다.


List<T>.Sort() 메서드 3. IComparable<T> 구현

IComparable 인터페이스를 상속받는 클래스에 CompareTo() 메서드를 재정의합니다.

 

해당 클래스에 CompareTo() 메서드가 정의되어 있으므로 인수가 없는 Sort() 메서드를 호출할 수 있습니다.

public class Student : IComparable<Student>
{
    public string Name { get; set; }
    public int Age { get; set; } = 0;
    public int Score { get; set; } = 0;

    public int CompareTo(Student studentObj)
    {
        if (studentObj == null)
        {
            return 1;
        }

        // Name 속성부터 비교
        int ret1 = String.Compare(this.Name, studentObj.Name);

        // Name 속성 비교 후 Age 속성 비교
        int ret2 = ret1 != 0 ? ret1 : this.Age.CompareTo(studentObj.Age);

        // Age 속성 비교 후 Score 속성 비교
        return ret2 != 0 ? ret2 : this.Score.CompareTo(studentObj.Score);
    }
}

class Program
{
    static void Main(string[] args)
    {
        List<Student> studentList = new List<Student>() {
            new Student() { Name = "Bill",  Age = 38, Score = 60 },
            new Student() { Name = "Fred",  Age = 22, Score = 80 },
            new Student() { Name = "Tom",   Age = 30, Score = 90 },
            new Student() { Name = "Bill",  Age = 38, Score = 90 },
            new Student() { Name = "Bill",  Age = 29, Score = 65 },
            new Student() { Name = "Steve", Age = 20, Score = 80 },
            new Student() { Name = "Bill",  Age = 28, Score = 70 },
            new Student() { Name = "Bill",  Age = 38, Score = 40 },
            new Student() { Name = "Sam",   Age = 25, Score = 50 },
            new Student() { Name = "Bill",  Age = 30, Score = 70 }
        };

        studentList.Sort();

        foreach (Student studentObj in studentList)
        {
            Console.WriteLine($"Name : {studentObj.Name} / Age : {studentObj.Age} / Score : {studentObj.Score}");
        }
    }
}

실행 결과

반응형

댓글