CodeGym /Các khóa học /C# SELF /Danh sách: List<T>

Danh sách: List<T>

C# SELF
Mức độ , Bài học
Có sẵn

1. Giới thiệu

Hãy tưởng tượng bạn đang có một danh sách mua sắm. Hôm nay cần mua 3 món, mai 10 món, ngày kia chỉ 1 món thôi. Nếu dùng mảng, bạn sẽ phải tạo mảng mới mỗi lần cho đúng số lượng món. Hoặc tạo một mảng siêu to "phòng hờ", thế là phải "vác" theo cả đống ô trống. Nghe không hiệu quả lắm nhỉ?

Giờ tưởng tượng bạn có một ba lô thần kỳ, tự động nở ra khi bạn bỏ thêm đồ vào, và co lại khi bạn lấy đồ ra. Bạn không cần biết trước sẽ bỏ bao nhiêu món. Cứ bỏ vào thôi, ba lô tự xử. Đó chính là List<T> trong thế giới lập trình C#!

List<T> — là một collection generic cho phép lưu trữ dãy phần tử cùng kiểu. Từ khóa ở đây là "động". Khác với mảng, List<T> có thể thay đổi kích thước khi chương trình chạy. Nó tự quản lý dung lượng bên trong, tăng lên khi cần.

Chữ T trong List<T> nghĩa là gì? Đó là tham số kiểu. Khi tạo List, bạn chỉ định kiểu dữ liệu sẽ lưu thay cho T. Ví dụ, List<int> chỉ lưu số nguyên, List<string> chỉ lưu chuỗi, còn List<double> chỉ lưu số thực. Như vậy bạn không thể lỡ tay bỏ táo vào danh sách cam, rất quan trọng để code ổn định và dễ đoán.

Dùng để làm gì?

Trong dự án thực tế, rất hay gặp trường hợp bạn không biết trước sẽ có bao nhiêu phần tử. Ví dụ:

  • Danh sách user vừa đăng nhập vào web.
  • Danh sách sản phẩm trong giỏ hàng online.
  • Danh sách kết quả tìm kiếm theo truy vấn.
  • Danh sách task trong trình quản lý công việc mà mình sắp code luôn nè.

Trong mấy trường hợp này, số lượng phần tử thay đổi liên tục, và List<T> là cứu tinh của tụi mình.

Các method cơ bản của List<T>

Method Mô tả Ví dụ
Add(T item)
Thêm phần tử vào cuối list
numbers.Add(5);
Insert(int idx, T)
Chèn vào vị trí chỉ định
numbers.Insert(0, 42);
Remove(T item)
Xóa lần xuất hiện đầu tiên của phần tử
numbers.Remove(100);
RemoveAt(int idx)
Xóa phần tử theo index
numbers.RemoveAt(2);
Clear()
Xóa sạch list
numbers.Clear();
Contains(T item)
Kiểm tra list có phần tử này không
numbers.Contains(10);
IndexOf(T item)
Index lần xuất hiện đầu tiên
int pos = numbers.IndexOf(42);
Count
Số lượng phần tử trong list
int n = numbers.Count;
Capacity
Dung lượng buffer bên trong
numbers.Capacity = 100;

2. Tạo list (List<T>)

Bắt tay vào thực hành nhé! Giả sử mình muốn làm một app quản lý task đơn giản. Cần một list để thêm các việc cần làm vào.

Để tạo list mới List<T> thì dùng toán tử new, giống như tạo object khác thôi.

Khai báo và tạo List như nào

Cú pháp tạo list khá giống mảng, chỉ khác là dùng ngoặc nhọn thay vì ngoặc vuông:

using System.Collections.Generic;

List<int> numbers = new List<int>();

Chú thích cho ai thích chi tiết:
- List<int> — là list số nguyên (int). Thay int bằng kiểu khác cũng được: string, double, class tự định nghĩa, v.v...
- Tạo xong thì list rỗng — độ dài là 0.

Ví dụ: bước đầu phát triển app của mình

Giả sử app của mình trước giờ cho user nhập tên và tuổi (từ mấy bài đầu). Giờ thêm chức năng lưu nhiều tên vào list để sau này xử lý tiếp.

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<string> names = new List<string>();

        Console.WriteLine("Nhập ba tên:");
        for (int i = 0; i < 3; i++)
        {
            string name = Console.ReadLine();
            names.Add(name);
        }

        Console.WriteLine("Bạn đã nhập:");
        foreach (string n in names)
        {
            Console.WriteLine(n);
        }
    }
}

Để ý method Add() nhé. Chính nó thêm phần tử mới vào list. Nhờ vậy list mới "động" được!

Có thể tạo list có sẵn phần tử luôn!

List<string> planets = new List<string> { "Sao Thủy", "Sao Kim", "Trái Đất" };

Rất tiện nếu bạn biết sẵn "đội hình xuất phát". Cú pháp này hay dùng trong test hoặc khi khởi tạo dữ liệu cố định.

3. Các thao tác cơ bản với List<T>

Trước khi học trick nâng cao, mình cùng ôn lại "bảng chữ cái" của list đã.

Thêm phần tử: Add()

Dùng method Add() — giống như bỏ thêm đồ lên kệ:

List<int> numbers = new List<int>();
numbers.Add(42);
numbers.Add(100);

Giờ bên trong: [42, 100]

Lấy số lượng phần tử: Count

Khác với mảng, list có property tên là Count chứ không phải Length:

int count = numbers.Count; // Trả về 2

Nhớ nhé: list dùng Count, còn mảng và chuỗi thì Length. Hai property khác nhau nhưng mục đích giống nhau.

Truy cập phần tử theo index

Có hai cách, thực ra giống nhau:

int first = numbers[0]; // 42
numbers[1] = 200; // Đổi 100 thành 200

Lưu ý: Index bắt đầu từ 0 như mảng. Đừng thử numbers[3] nếu chỉ có ba phần tử nhé!

Duyệt qua các phần tử trong list

Dùng vòng lặp for hoặc foreach đều được:

// Dùng for — nếu cần index
for (int i = 0; i < numbers.Count; i++)
{
    Console.WriteLine(numbers[i]);
}

// Dùng foreach — nếu không cần index
foreach (int number in numbers)
{
    Console.WriteLine(number);
}

foreach thường đơn giản, dễ hiểu, nhất là với collection lớn.
Nhưng dùng foreach thì không sửa được phần tử theo index hoặc break sớm khỏi vòng lặp (trừ khi dùng trick khác).

Chèn phần tử theo index: Insert()

Method Insert() dùng khi bạn muốn thêm phần tử không phải ở cuối list mà ở bất kỳ đâu. Ví dụ, chèn vào vị trí thứ hai:

numbers.Insert(1, 55); // Chèn 55 vào vị trí index 1

Giờ numbers: [42, 55, 200]

Xóa phần tử

Xóa khỏi list cũng dễ như thêm vào:

numbers.Remove(55); // Xóa lần xuất hiện ĐẦU TIÊN của 55
numbers.RemoveAt(0); // Xóa phần tử ở index 0
numbers.Clear(); // Xóa sạch list

Lưu ý khi xóa:
- Nếu xóa giá trị không có trong list bằng Remove, cũng không sao: list không đổi, không báo lỗi.
- Sau Clear() thì list lại rỗng.

4. Một số lưu ý hay ho

List<T> "lớn lên" như thế nào

Khi bạn thêm phần tử vào List<T>, nó không tăng kích thước từng chút một đâu. Thay vào đó, List<T> sẽ cấp phát sẵn một block bộ nhớ (dung lượng bên trong — Capacity).

Khi số phần tử vượt quá dung lượng hiện tại, list sẽ cấp phát block lớn hơn (thường gấp đôi) và copy toàn bộ dữ liệu cũ sang:


Thêm phần tử → vẫn đủ → vẫn đủ → vẫn đủ →
Thêm nữa không đủ → tạo mảng lớn hơn → copy dữ liệu → tiếp tục

Nhờ vậy thêm phần tử nhanh hơn, vì không phải cấp phát lại mỗi lần Add, chỉ thỉnh thoảng thôi.

var list = new List<int>();    // Capacity = 0
list.Add(1);                   // Capacity = 4
list.Add(2);                   // Capacity = 4
list.Add(3);                   // Capacity = 4
list.Add(4);                   // Capacity = 4
list.Add(5);                   // Capacity = 8 (tăng dung lượng!)

Capacity vs. Count: khác nhau chỗ nào?

  • Count — số phần tử đã thêm vào list.
  • Capacity — kích thước mảng bên trong lưu phần tử.
List<int> nums = new List<int>();
Console.WriteLine(nums.Count); // 0
Console.WriteLine(nums.Capacity); // Thường là 0 hoặc nhỏ

nums.Add(123);
Console.WriteLine(nums.Count); // 1
Console.WriteLine(nums.Capacity); // Có thể lớn hơn Count

nums.Capacity = 1000; // Có thể tăng Capacity nếu biết sẽ có nhiều phần tử

Đa số dev chỉ dùng Count, nhưng nếu bạn làm app cần tối ưu hiệu năng (game, phân tích dữ liệu...), chỉnh Capacity cũng hữu ích.

5. Ví dụ

Tiếp tục phát triển app nhỏ của mình — giờ user có thể thêm số yêu thích, rồi thao tác với list: chèn, xóa, in ra, xóa sạch.

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<int> favoriteNumbers = new List<int>();

        Console.WriteLine("Nhập số yêu thích của bạn (mỗi số một dòng, 0 để kết thúc):");
        while (true)
        {
            int num = int.Parse(Console.ReadLine());
            if (num == 0) break;
            favoriteNumbers.Add(num);
        }

        Console.WriteLine("Số yêu thích của bạn:");
        foreach (int n in favoriteNumbers)
        {
            Console.Write(n + " ");
        }

        Console.WriteLine("\nMuốn xóa số đầu tiên không? (y/n)");
        if (Console.ReadLine().ToLower() == "y")
        {
            favoriteNumbers.RemoveAt(0);
        }

        Console.WriteLine("Giờ list của bạn là:");
        foreach (int n in favoriteNumbers)
        {
            Console.Write(n + " ");
        }
    }
}

Lưu ý: Ở đây mình dùng int.Parse. Trong dự án thực tế, nên kiểm tra input bằng int.TryParse để tránh app bị crash nếu user nhập sai.

6. Lỗi phổ biến và "bẫy" cho newbie

Rất hay gặp trường hợp: bạn viết vòng lặp từ 0 đến List.Count, nhưng trong vòng lặp (ví dụ trong foreach) lại sửa list (thêm hoặc xóa phần tử). Đừng làm vậy nhé! Dân pro gọi là "modify collection khi đang duyệt". Thường sẽ bị lỗi InvalidOperationException ngay lập tức.

Ví dụ code sai:

foreach (int n in numbers)
{
    if (n < 0)
        numbers.Remove(n); // BÙM! Runtime error.
}

Cách đúng: hoặc gom index/phần tử cần xóa vào list khác trước, hoặc dùng vòng lặp for chạy ngược.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION