Hướng Dẫn Sắp Xếp Tùy Chỉnh Cho Danh Sách List<T> Trong C#

Trong C#, việc sắp xếp một danh sách các đối tượng, đặc biệt là các kiểu tùy chỉnh như List<NhanVien>, yêu cầu định nghĩa logic so sánh cụ thể. Bài viết này sẽ trình bày các phương pháp phổ biến để thực hiện sắp xếp tùy chỉnh cho List<T>.

Định nghĩa lớp Đối tượng Mẫu: Nhân Viên

Để minh họa, chúng ta sẽ sử dụng một lớp NhanVien (Nhân viên) đơn giản với các thuộc tính cơ bản:

public class NhanVien
{
    public string HoTen { get; set; } // Tên đầy đủ của nhân viên
    public int Tuoi { get; set; }    // Tuổi của nhân viên
    public string MaSo { get; set; } // Mã số nhân viên (dùng cho sắp xếp đa trường)

    // Có thể triển khai giao diện IComparable<NhanVien> tại đây nếu cần
    // Nhưng để giữ ví dụ đơn giản, chúng ta sẽ không làm vậy ngay từ đầu.
}

Các Phương Pháp Sắp Xếp Tùy Chỉnh cho List<NhanVien>

1. Sử dụng Giao Diện IComparer<T>

Phương pháp này yêu cầu bạn tạo một lớp riêng biệt triển khai giao diện IComparer<T>. Lớp này sẽ chứa logic so sánh cụ thể của bạn và được truyền làm tham số cho phương thức Sort() của List<T>.

public class SoSanhNhanVienTheoTuoi : IComparer<NhanVien>
{
    public int Compare(NhanVien nv1, NhanVien nv2)
    {
        // So sánh hai đối tượng NhanVien dựa trên thuộc tính Tuoi.
        // Trả về số âm nếu nv1 nhỏ hơn nv2
        // Trả về 0 nếu nv1 bằng nv2
        // Trả về số dương nếu nv1 lớn hơn nv2
        return nv1.Tuoi.CompareTo(nv2.Tuoi);
    }
}

// Cách sử dụng phương thức Sort() với một IComparer tùy chỉnh:
List<NhanVien> danhSachNhanVien = new List<NhanVien>
{
    new NhanVien { HoTen = "An", Tuoi = 30, MaSo = "NV001" },
    new NhanVien { HoTen = "Binh", Tuoi = 25, MaSo = "NV002" },
    new NhanVien { HoTen = "An", Tuoi = 35, MaSo = "NV003" } // Nhân viên tên An khác tuổi
};

Console.WriteLine("Danh sách nhân viên ban đầu:");
foreach (var nv in danhSachNhanVien)
{
    Console.WriteLine($"- {nv.HoTen}, Tuổi: {nv.Tuoi}, Mã số: {nv.MaSo}");
}

danhSachNhanVien.Sort(new SoSanhNhanVienTheoTuoi());

Console.WriteLine("\nDanh sách sau khi sắp xếp theo tuổi (tăng dần):");
foreach (var nv in danhSachNhanVien)
{
    Console.WriteLine($"- {nv.HoTen}, Tuổi: {nv.Tuoi}, Mã số: {nv.MaSo}");
}

2. Sử dụng Biểu Thức Lambda hoặc Phương Thức Ẩn Danh

Đây là cách tiếp cận linh hoạt và ngắn gọn, cho phép bạn định nghĩa logic so sánh trực tiếp tại chỗ khi gọi phương thức Sort(). Biểu thức Lambda thường được ưa chuộng hơn vì cú pháp gọn gàng.

// Sắp xếp theo tuổi bằng biểu thức Lambda
// nvA và nvB là hai đối tượng NhanVien cần so sánh.
danhSachNhanVien.Sort((nvA, nvB) => nvA.Tuoi.CompareTo(nvB.Tuoi));

Console.WriteLine("\nDanh sách sau khi sắp xếp lại theo tuổi bằng Lambda:");
foreach (var nv in danhSachNhanVien)
{
    Console.WriteLine($"- {nv.HoTen}, Tuổi: {nv.Tuoi}, Mã số: {nv.MaSo}");
}

// Sắp xếp theo họ tên bằng phương thức ẩn danh (cú pháp dài hơn Lambda)
danhSachNhanVien.Sort(delegate(NhanVien nvA, NhanVien nvB)
{
    return nvA.HoTen.CompareTo(nvB.HoTen);
});

Console.WriteLine("\nDanh sách sau khi sắp xếp theo họ tên bằng phương thức ẩn danh:");
foreach (var nv in danhSachNhanVien)
{
    Console.WriteLine($"- {nv.HoTen}, Tuổi: {nv.Tuoi}, Mã số: {nv.MaSo}");
}

3. Triển Khai Giao Diện IComparable<T> Trong Lớp Đối Tượng

Nếu logic sắp xếp là "mặc định" hoặc "tự nhiên" cho đối tượng của bạn, bạn có thể triển khai giao diện IComparable<T> ngay bên trong định nghĩa lớp. Khi đó, phương thức Sort() không tham số của List<T> sẽ tự động sử dụng logic so sánh này.

public class NhanVien : IComparable<NhanVien> // Triển khai giao diện IComparable
{
    public string HoTen { get; set; }
    public int Tuoi { get; set; }
    public string MaSo { get; set; }

    // Triển khai phương thức CompareTo cho giao diện IComparable<NhanVien>
    public int CompareTo(NhanVien nhanVienKhac)
    {
        // Xử lý trường hợp đối tượng cần so sánh là null
        if (nhanVienKhac == null) return 1;

        // So sánh chính theo tuổi
        int ketQuaTuoi = this.Tuoi.CompareTo(nhanVienKhac.Tuoi);
        if (ketQuaTuoi != 0)
        {
            return ketQuaTuoi;
        }

        // Nếu tuổi bằng nhau, tiếp tục sắp xếp theo Mã số để có thứ tự ổn định
        return this.MaSo.CompareTo(nhanVienKhac.MaSo);
    }
}

// Cách sử dụng (sau khi lớp NhanVien đã triển khai IComparable<NhanVien>):
List<NhanVien> danhSachNhanVienMoi = new List<NhanVien>
{
    new NhanVien { HoTen = "An", Tuoi = 30, MaSo = "NV001" },
    new NhanVien { HoTen = "Binh", Tuoi = 25, MaSo = "NV002" },
    new NhanVien { HoTen = "An", Tuoi = 35, MaSo = "NV003" },
    new NhanVien { HoTen = "Binh", Tuoi = 25, MaSo = "NV004" } // Cùng tuổi với NV002
};

Console.WriteLine("\nDanh sách ban đầu (trước IComparable.Sort()):");
foreach (var nv in danhSachNhanVienMoi)
{
    Console.WriteLine($"- {nv.HoTen}, Tuổi: {nv.Tuoi}, Mã số: {nv.MaSo}");
}

danhSachNhanVienMoi.Sort(); // Gọi phương thức Sort() không tham số

Console.WriteLine("\nDanh sách sau khi sắp xếp theo tuổi và mã số (mặc định của NhanVien):");
foreach (var nv in danhSachNhanVienMoi)
{
    Console.WriteLine($"- {nv.HoTen}, Tuổi: {nv.Tuoi}, Mã số: {nv.MaSo}");
}

4. Sử dụng Phương Thức LINQ OrderBy hoặc ThenBy (Tạo danh sách mới)

Nếu bạn muốn tạo một danh sách mới đã được sắp xếp mà không thay đổi thứ tự của danh sách gốc, LINQ cung cấp các phương thức mở rộng OrderBy()ThenBy() rất tiện lợi. Các phương thức này trả về một IOrderedEnumerable<T>, thường được chuyển đổi thành List<T> bằng ToList().

List<NhanVien> danhSachGoc = new List<NhanVien>
{
    new NhanVien { HoTen = "Cường", Tuoi = 30, MaSo = "NV003" },
    new NhanVien { HoTen = "An", Tuoi = 25, MaSo = "NV001" },
    new NhanVien { HoTen = "Dũng", Tuoi = 30, MaSo = "NV002" },
    new NhanVien { HoTen = "Bình", Tuoi = 25, MaSo = "NV004" }
};

Console.WriteLine("\nDanh sách gốc (LINQ):");
foreach (var nv in danhSachGoc)
{
    Console.WriteLine($"- {nv.HoTen}, Tuổi: {nv.Tuoi}, Mã số: {nv.MaSo}");
}

// Sắp xếp theo tuổi (tăng dần) và tạo danh sách mới
var danhSachSapXepTheoTuoi = danhSachGoc.OrderBy(nv => nv.Tuoi).ToList();

Console.WriteLine("\nDanh sách sắp xếp theo tuổi (LINQ OrderBy):");
foreach (var nv in danhSachSapXepTheoTuoi)
{
    Console.WriteLine($"- {nv.HoTen}, Tuổi: {nv.Tuoi}, Mã số: {nv.MaSo}");
}

// Sắp xếp theo tuổi, sau đó theo họ tên (khi tuổi bằng nhau)
var danhSachSapXepDaTruong = danhSachGoc
    .OrderBy(nv => nv.Tuoi)      // Tiêu chí sắp xếp chính: theo tuổi
    .ThenBy(nv => nv.HoTen)      // Tiêu chí phụ: theo họ tên (khi tuổi bằng nhau)
    .ToList();

Console.WriteLine("\nDanh sách sắp xếp theo tuổi rồi đến họ tên (LINQ OrderBy/ThenBy):");
foreach (var nv in danhSachSapXepDaTruong)
{
    Console.WriteLine($"- {nv.HoTen}, Tuổi: {nv.Tuoi}, Mã số: {nv.MaSo}");
}

// Sắp xếp giảm dần sử dụng OrderByDescending
var danhSachSapXepGiamDan = danhSachGoc.OrderByDescending(nv => nv.Tuoi).ToList();

Console.WriteLine("\nDanh sách sắp xếp giảm dần theo tuổi (LINQ OrderByDescending):");
foreach (var nv in danhSachSapXepGiamDan)
{
    Console.WriteLine($"- {nv.HoTen}, Tuổi: {nv.Tuoi}, Mã số: {nv.MaSo}");
}

Thẻ: C# list sorting IComparer IComparable

Đăng vào ngày 12 tháng 8 lúc 14:59