C# cơ bản .NET Core
FileStream (Bài trước)
(Bài tiếp) SortedList

Collection trong C#

Một collection (bộ, tập hợp) là một nhóm các đối tượng có sự liên quan đến nhau. Số đối tượng trong collect có thể thay đổi tăng giảm. Có nhiều loại collection, chúng được tập hợp vào namespace System.Collections. Thường thì một lớp collection có các phương thức để thêm, bớt, lấy tổng phần tử.

.NET cung cấp một số các lớp collection kiểu Generic như: List<T>, Dictionary<TKey, TValue>, Stack<T> ... những lớp generic này ở namespace System.Collections.Generic

Tìm hiểu phần này bạn cần nắm vững về Generic trong C# trước

Ngoài ra namespace System.Collections cũng có các lớp collection mà không sử dụng generic như: ArrayList, Stack, Queue ...

Các giao diện - interface về collect mà bạn có thể sử dụng

Interface Mô tả
IEnumerable<T> Triển khai nó nếu muốn duyệt phần tử bằng foreach, nó định nghĩa phương thức GetEnumerator trả về một enumerator.
ICollection<T> Giao diện này được triển khai bở các generic collection. Với nó lấy tổng phần tử bằng thuộc tính Count, copy các phần tử vào mảng bằng CopyTo, thêm bớt phần tử với Add, Remove, Clear
IList<T> Giao diện này kế thừa ICollection<T> là một danh sách các phần tử truy cập được theo vị trí của nó. Nó có indexer, phương thức để chèn phần tử xóa phần tử Insert RemoveAt.
ISet<T> Giao diện triển khai bởi các tập hợp
IDictionary<TKey,TValue> Giao diện để triển khai loại dữ liệu lưu trữ theo cặp key, value.
ILookup<TKey,TValue> Giao diện để triển khai loại dữ liệu lưu trữ theo cặp key, value. Nhưng cho phép một key có nhiều giá trị
IComparer<TKey,TValue> Giao diện để triển khai cho phép so sánh để sắp xếp Collection
IEqualityComparer<TKey,TValue> Giao diện để triển khai cho phép so sánh bằng

Lớp List<T>

Lớp collection List là lớp triển khai các giao diện IList, ICollection, IEnumerable nó quản lý danh sách các đối tượng cùng kiểu. Bạn có thể thêm, bớt, truy cập, sắp xếp các phần tử trong danh sách bằng các phương thức nó cung cấp như Add, AddRange, Insert, RemoveAt, Remove ... các phương thức này chi tiết theo ví dụ dưới

Khởi tạo một danh sách List, mà các phần tử có kiểu element_type:

var list = new List<element_type>();

Ví dụ, xây dựng danh sách các sản phẩm, sản phẩm có kiểu Product tự định nghĩa như sau - lớp sản phẩm hỗ trợ so sánh với sản phẩm khác nên triển khai IComparable, cho phép hiện lấy một chuỗi thông tin bằng ToString với định dạng nào đó nên triển khai giao diện IFormattable

Mã nguồn xây dựng lớp Product trong file Product.cs như sau:

using System;
namespace CS017_Generic
{
    public class Product : IComparable<Product>, IFormattable
    {
        public int ID {set; get;}
        public string Name {set; get;}        // tên
        public double Price {set; get;}       // giá
        public string Origin {set; get;}      // xuất xứ

        public Product(int id, string name, double price, string origin) {
            ID = id; Name = name; Price = price; Origin = origin;
        }

        //Triển khai IComparable, cho biết vị trí sắp xếp so với đối tượng khác
        // trả về 0 - cùng vị trí; trả về > 0 đứng sau other; < 0 đứng trước trong danh sách
        public int CompareTo(Product other)
        {
            // sắp xếp về giá
            double delta = this.Price - other.Price;
            if (delta > 0)      // giá lớn hơn xếp trước
                return -1;
            else if (delta < 0) // xếp sau, giá nhỏ hơn
                return 1;
            return 0;

        }
        // Triển khai IFormattable, lấy chuỗi thông tin của đối tượng theo định dạng
        // format hỗ trợ "O" và "N"
        public string ToString(string format, IFormatProvider formatProvider)
        {
            if (format == null) format = "O";
            switch (format.ToUpper()) {
                case "O": // Xuất xứ trước
                    return $"Xuất xứ: {Origin} - Tên: {Name} - Giá: {Price} - ID: {ID}";
                case "N": // Tên xứ trước
                    return $"Tên: {Name} - Xuất xứ: {Origin} - Giá: {Price} - ID: {ID}";
                default: // Quăng lỗi nếu format sai
                    throw new FormatException("Không hỗ trợ format này");
            }
        }

        // Nạp chồng ToString
        override public string ToString() => $"{Name} - {Price}";

        // Quá tải thêm ToString - lấy chỗi thông tin sản phẩm theo định dạng
        public string ToString(string format) => this.ToString(format, null);

    }
}

Khởi tạo List<T>

Để khởi tạo một danh sách rỗng, dùng toán tử new

var numbers  = new List<int>();           // danh sách số nguyên
var products = new List<Product>();       // danh sách Product

Khởi tạo danh sách có sẵn một số phần tử, thì các phần tử liệt kê sau {}

var numbers  = new List<int>() {1,2,3,4};     // khởi tạo 4 phần tử
var products = new List<Product>()            // khởi tạo 1 phần tử
{
     new Product(1, "Iphone 6", 100, "Trung Quốc")
};

Thêm, xóa, chèn và đọc phần tử trong List<T>

1 THÊM PHẦN TỬ vào cuối danh sách sử dụng phương thức Add

var p = new Product(2, "IPhone 7", 200, "Trung Quốc");
products.Add(p);                                                // Thêm p vào cuối List
products.Add(new Product(3, "IPhone 8", 400, "Trung Quốc"));    // thêm đối tượng mới vào cuối List

Nếu muốn thêm nhiều phần tử một lúc (mảng các phần tử), dùng AddRange

var arrayProducts = new Product[]                  // Mảng 2 phần tử
{
    new Product(4, "Glaxy 7", 500, "Việt Nam"),
    new Product(5, "Glaxy 8", 700, "Việt Nam"),
};
products.AddRange(arrayProducts);                   // Nối các phần tử của mảng vào danh sách

2 CHÈN PHẦN TỬ vào danh sách, phần tử sẽ ở vị trí chỉ ra dùng phương thức Insert(index, object) hoặc chèn cả một mảng InsertRange(index,, arrayObject). Trong đó index là vị trí chèn phần tử (0 là đầu tiên).

products.Insert(3, new Product(6, "Macbook Pro", 1000, "Mỹ"));     // chèn phần tử vào vị trí index 3, (thứ 4)

3 ĐỌC PHẦN TỬ trong List bạn dùng indexer với chỉ số (chỉ số bắt đầu từ 0). Ví dụ lấy phần tử ở Index = 1;

var pro = products[2];                                             // đọc phần tử có index = 2
Console.WriteLine(pro.ToString());

Để duyệt qua các phần tử bạn có thể dùng lệnh for hoặc foreach

// Duyệt qua tất cả các phần tử bằng for
// products.Count = lấy tổng phần tử trong List
for (int i = 1; i < products.Count; i++)
{
    var pi = products[i - 1];
    Console.WriteLine(pi.ToString());
}

// Duyệt qua các phần tử bằng foreach
foreach (var pi in products)
{
    Console.WriteLine(pi.ToString());
}

4 XÓA PHẦN TỬ trong List - để xóa phần tử ở vị trí index dùng RemoveAt(index), để xóa cả một đoạn count phần tử, từ vị trí index dùng RemoveRange(index, count);, để xóa toàn bộ (làm rỗng) gọi Clear(); hoặc RemoveAll();

products.RemoveAt(0);                           // xóa phần tử đầu tien
products.RemoveRange(products.Count - 2, 2);    // xóa 2 phần tử cuối

Khi bạn có tham chiếu đến đối tượng đang có trong List, cũng có thể loại nó bằng Remove(obj);

var pro_rm = products[1];
products.Remove(pro_rm);                        // xóa phần tử pro_rm

Tìm kiếm thông tin trong List

Một số phương thức cho phép tìm kiếm, tra cứu vị trị trí các phần tử trong List

Phương thức Mô tả
IndexOf(obj) Tìm index của đối tượng trong List
LastIndexOf(obj) Tìm index của phần tử cuối cùng có giá trị bằng obj trong List
FindIndex Tìm kiếm trả về Index
FindLastIndex Tìm kiếm trả về Index cuối
Find(Predicate) Tìm kiếm trả về phần tử
FindAll(Predicate) Tìm kiếm trả về danh sách phần tử
FindLast Tìm kiếm trả về phần tử cuối tìm thấy

Trong các phương thức trên, có các phương thức ví dụ Find, chứa tham số là delegate bool Predicate<in T>(T obj);, nó là hàm callback, trả về true là phần tử phù hợp trả về (xem về sử dụng delegate trong C#)

Ví dụ sau đây là một Delegate phù hợp gán cho tham số Predicate

// Delegate trả về true khi tên bằng "Glaxy 8"
(Product ob) => {
    return (ob.Name == "Glaxy 8");
}

Đoạn mã này có thể làm tham số cho Find, FindAll ...

Product foundpr1 = products.Find(
    (Product ob) => { return (ob.Name == "Glaxy 8");}
);
if (foundpr1 != null)
    Console.WriteLine("(found) " + foundpr1.ToString("O"));
// (found) Xuất xứ: Việt Nam - Tên: Glaxy 8 - Giá: 700 - ID: 5

Các delegate cũng có thể viết gọn lại

// tìm index của đối tượng có xuất xứ là "Trung Quốc"
var ifound = products.FindIndex(x => x.Origin == "Trung Quốc");

// tìm các sản phẩm có giá trên 100
List<Product> p_100 = products.FindAll(product => product.Price > 100);

Nếu muốn tùy biến cao hơn Delegate, để tìm kiếm theo tham số tùy chọn, bạn có thể để Delegate trên vào lớp chức năng, ví dụ xây dựng lớp SearchNameProduct

public class SearchNameProduct {
    string namesearch;
    public SearchNameProduct(string name) {
        namesearch = name;
    }
    // Hàm gán cho delegage
    public bool search(Product p) {
        return p.Name == namesearch;
    }
}

Thực hiện tìm kiếm, ví dụ

Product pr1 = products.Find( (new SearchNameProduct("Glaxy 8")).search);        // Tìm sản phẩm có tên Glaxy 8
Product pr2 = products.Find( (new SearchNameProduct("IPhone 6")).search);       // Tìm sản phẩm có tên IPhone 6

Sắp xếp các phần tử trong List

Để sắp xếp các phần tử trong danh sách, nếu phần tử đó có triển khai giao diện IComparable thì chỉ việc gọi Sort() để có danh sách theo thứ tự.

Ví dụ trên, lớp Product có triển khai IComparable, với phương thức CompareTo, thì sản phẩm nào có giá cao hơn xếp trước, có giá thấp hơn xếp sau.

products.Sort();
foreach (var pi in products)
{
    Console.WriteLine(pi.ToString("N"));
}
Kết quả
Tên: Macbook Pro - Xuất xứ: Mỹ - Giá: 1000 - ID: 6
Tên: Glaxy 8 - Xuất xứ: Việt Nam - Giá: 700 - ID: 5
Tên: Glaxy 7 - Xuất xứ: Việt Nam - Giá: 500 - ID: 4
Tên: IPhone 8 - Xuất xứ: Trung Quốc - Giá: 400 - ID: 3
Tên: IPhone 7 - Xuất xứ: Trung Quốc - Giá: 200 - ID: 2
Tên: Iphone 6 - Xuất xứ: Trung Quốc - Giá: 100 - ID: 1

Bạn cũng có thể tùy biến cách thức sắp xếp bằng cách cung cấp hàm callback dạng deleage hai tham số kiểu cùng với kiểu phần tử cho Search, thay vì sắp xếp mặc định như trên.

Nhớ là trả về > 0 thì phần tử hiện tại xếp sau phần tử tham số.

Ví dụ hàm callback sau xếp ID nhỏ lên trước

products.Sort(

    (p1, p2) => {
        if (p1.ID > p2.ID)
            return 1;
        else if (p1.ID == p2.ID)
            return  0;
        return   -1;
    }

);
foreach (var pi in products)
{
    Console.WriteLine(pi.ToString("N"));
}

Một số phương thức khác tham khảo

  • Contains(obj) kiểm tra có chứa phần tử obj
  • Reverse() đảo thứ tự danh sách
  • ToArray() copy các phần tử ra mảng

Tham khảo mã nguồn ví dụ: CS017_GenericCollect (Method) - Git hoặc tải về excs017-3 (List)


Đăng ký nhận bài viết mới
FileStream (Bài trước)
(Bài tiếp) SortedList