[C#] Sorting Collections with Custom Rules by Implementing IComparer

When using the List<T>.Sort() method, there are cases where you want to sort based on a specific property or a special calculation formula, rather than the default order (e.g., ascending order).

Implementing IComparable<T> on the class itself changes the “default order” of that class. However, by creating a separate class that implements the IComparer<T> interface and passing it to the Sort method, you can apply a specific sort order only for that instance.

In this article, I will explain this using a scenario where schedule data with different dates is sorted “in order of earliest time (hour/minute), regardless of the date.”

目次

Table of Contents

  1. Overview of the IComparer<T> Interface
  2. Practical Code Example: Sorting by Time Ignoring Date
  3. Execution Result
  4. Technical Points and Applications
    1. Reusability of Comparison Logic
    2. Simplification with Comparison<T> Delegate
    3. Difference from LINQ’s OrderBy
  5. Summary

Overview of the IComparer<T> Interface

IComparer<T> provides a method named Compare for comparing two objects.

int Compare(T x, T y)

The rules for the return value are the same as IComparable<T>:

  • Negative value: x is smaller than y (comes before).
  • 0: x and y are equal.
  • Positive value: x is larger than y (comes after).

Practical Code Example: Sorting by Time Ignoring Date

The following code is an example of creating and using a dedicated comparison class TimeOfDayComparer to sort a list of schedules spanning several days into “earliest time of day” order.

using System;
using System.Collections.Generic;

namespace ScheduleApp
{
    class Program
    {
        static void Main()
        {
            // List of schedules with scattered dates
            var schedules = new List<DateTime>
            {
                new DateTime(2025, 10, 1, 19, 30, 0), // 10/1 19:30
                new DateTime(2025, 10, 2, 09, 00, 0), // 10/2 09:00
                new DateTime(2025, 10, 1, 12, 15, 0), // 10/1 12:15
                new DateTime(2025, 10, 3, 08, 45, 0), // 10/3 08:45
            };

            Console.WriteLine("--- Normal Sort (Date and Time) ---");
            schedules.Sort(); // Default comparison includes the date
            foreach (var dt in schedules)
            {
                Console.WriteLine(dt.ToString("MM/dd HH:mm"));
            }

            Console.WriteLine("\n--- Custom Sort (Time Only) ---");
            // Sort by passing an instance of the custom Comparer
            schedules.Sort(new TimeOfDayComparer());
            
            foreach (var dt in schedules)
            {
                Console.WriteLine(dt.ToString("MM/dd HH:mm"));
            }
        }
    }

    /// <summary>
    /// Class to compare only by time, ignoring the date part of DateTime.
    /// </summary>
    public class TimeOfDayComparer : IComparer<DateTime>
    {
        public int Compare(DateTime x, DateTime y)
        {
            // Get TimeOfDay property (TimeSpan type) and compare.
            // This ensures size judgment is based only on time, even if year/month/day differ.
            return x.TimeOfDay.CompareTo(y.TimeOfDay);

            // Reference: If you want to compare only by Hour:
            // return x.Hour.CompareTo(y.Hour);
        }
    }
}

Execution Result

--- Normal Sort (Date and Time) ---
10/01 12:15
10/01 19:30
10/02 09:00
10/03 08:45

--- Custom Sort (Time Only) ---
10/03 08:45
10/02 09:00
10/01 12:15
10/01 19:30

Technical Points and Applications

1. Reusability of Comparison Logic

The advantage of creating a class implementing IComparer<T> (like TimeOfDayComparer here) is that the comparison logic can be reused as a component. This is very convenient when the same sort order is needed in multiple places or when performing unit tests.

2. Simplification with Comparison<T> Delegate

If you do not plan to reuse the comparison logic and only need it once, you can write it concisely using a lambda expression (Comparison<T> delegate) without defining a separate class.

// Pass comparison logic directly via lambda expression
schedules.Sort((x, y) => x.TimeOfDay.CompareTo(y.TimeOfDay));

3. Difference from LINQ’s OrderBy

List<T>.Sort sorts the list itself (in-place processing). On the other hand, LINQ’s OrderBy method does not modify the original list but returns a new sequence that is sorted. You can also use IComparer<T> with OrderBy.

// Usage with LINQ
var sortedQuery = schedules.OrderBy(x => x, new TimeOfDayComparer());

Summary

By implementing IComparer<T>, you can enable sorting from a unique perspective that differs from the object’s default order. This is the standard technique for achieving flexible sorting according to requirements, such as “ignoring dates,” “case-insensitive sorting,” or “sorting by specific ID order.”

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次