CodeGym /Các khóa học /C# SELF /Bắt biến bằng local function

Bắt biến bằng local function

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

1. Giới thiệu

Nhớ lại về scope

Hãy tưởng tượng biến giống như nhân viên trong một văn phòng lớn, còn method, vòng lặp và block code là các phòng và văn phòng nhỏ. Một số nhân viên chỉ được vào phòng của mình, số khác thì đi khắp tòa nhà. Việc ai ở đâu chính là scope (scope) của họ.

Scope xác định nơi biến được khai báo sẽ “thấy” trong chương trình và nơi bạn có thể dùng nó.

Các loại scope chính

Trong C# có mấy loại scope cơ bản như sau:

Scope Ví dụ Biến "thấy" ở đâu
Local Bên trong method hoặc block Chỉ trong block đó
Tham số method Trong signature của method Chỉ trong method đó
Biến class (field) Trong thân class ngoài method Trong mọi method của class đó
Biến trong vòng lặp/điều kiện Bên trong
{}
của vòng lặp/if
Chỉ trong
{}
đó

Ví dụ có giải thích

public class Office
{
    
  
int buildingNumber = 50; // Field của class: thấy ở mọi method public void PrintInfo() {
int roomNumber = 101; // Biến local: chỉ thấy trong PrintInfo if (roomNumber > 100) {
int deskNumber = 5; // Chỉ thấy trong block if này Console.WriteLine(deskNumber);
} Console.WriteLine(deskNumber); // Lỗi! deskNumber ở đây không thấy nữa
}
}

2. Local function và scope

Ai "thấy" ai?

Khi bạn khai báo local function trong method (hoặc thậm chí trong vòng lặp hay điều kiện), nó nằm cùng scope với các biến khai báo phía trên. Local function kiểu như “một phần của phòng đó”.

Ví dụ

Local function thấy biến từ scope bên ngoài


    void PrintWithPrefix(string message)
{
    
  
string prefix = "[LOG]: "; void Print() {
Console.WriteLine( prefix + message); // thấy cả hai biến!
} Print();
}

Ở đây biến prefixmessage đều thấy được trong local function Print, vì chúng được khai báo cùng hoặc rộng hơn scope.

Có bao nhiêu scope ở đây?

Trong ví dụ trên:

  • có scope của method PrintWithPrefix
  • bên trong là scope của function Print
Local function không tạo “phòng” riêng hoàn toàn: nó vẫn thấy mọi thứ ngoài “hành lang” (method).

3. Bắt biến (Capture)

Bắt biến là khi local function dùng biến được khai báo ngoài function đó, nhưng vẫn trong cùng scope.

Local function và anonymous method (lambda, sẽ nói sau) sẽ nhớ (hoặc “bắt”) mọi biến mà nó thấy lúc khai báo.

Có thể nói function kiểu như chụp ảnh (capture) thế giới xung quanh — và có thể dùng biến đó kể cả khi gọi rất lâu sau.

Sơ đồ

Method Main
└─ biến x
└─ local function F() ← "bắt" x

Ví dụ — bắt biến đơn giản

void CounterExample()
{
    int counter = 0;

    void Increase()
    {
        counter++; // Function này bắt biến counter
    }

    Increase();
    Increase();
    Console.WriteLine(counter); // In ra 2
}
Local function bắt biến từ scope bên ngoài

Ở đây sau hai lần gọi local function Increase thì giá trị counter tăng lên 2.

4. Ứng dụng của bắt biến

Bắt biến giúp bạn “truyền” dữ liệu giữa scope của method và local function dễ dàng, không phải thêm tham số lằng nhằng.

Nếu không có capture, bạn sẽ phải truyền mọi biến qua tham số:

void CounterExampleWithoutCapture()
{
    int counter = 0;

    void Increase(ref int c)
    {
        c++;
    }

    Increase(ref counter);
    Increase(ref counter);
    Console.WriteLine(counter);
}

Cái này khá phiền — tại sao phải viết ref và làm xấu signature function, trong khi nó hoàn toàn có thể “thấy” biến bên ngoài?

5. Local function và vòng đời biến sau khi thoát method

Biến có sống lâu hơn method không?

Nếu bạn truyền local function (hoặc delegate với lambda) ra ngoài method hiện tại, biến đã bị bắt sẽ tự động không “chết” khi thoát method. CLR (.NET virtual machine) sẽ lo — mọi thứ cần thiết sẽ được “giữ lại” trong bộ nhớ.

Ví dụ: function sống ngoài method

Func<int> GetCounter()
{
    int count = 0;
    int Increment()
    {
        count++;
        return count;
    }
    return Increment; // Trả function ra ngoài!
}

var counter = GetCounter();
Console.WriteLine(counter()); // 1
Console.WriteLine(counter()); // 2
Closure: biến count vẫn sống sau khi thoát method

Ở đây, kể cả sau khi method GetCounter kết thúc, biến count vẫn còn sống, vì function trả về đã bắt nó. Cái này gọi là closure — sẽ nói kỹ hơn ở bài khác, nhưng với local function thì cơ chế cũng vậy.

6. Lỗi thường gặp và tình huống thú vị

Gán lại biến trước khi gọi local function

Đôi khi bạn sẽ gặp trường hợp biến mà local function bắt bị thay đổi trước khi gọi — và kết quả sẽ khác với mong đợi.

Ví dụ:


void Example()
{
    int x = 42;
    void PrintX() {  Console.WriteLine(x);  } 

    x = 100; // Biến bị đổi rồi!
    PrintX(); // In ra 100, không phải 42!
}

Mẹo: Local function luôn thấy giá trị mới nhất của biến tại thời điểm gọi.

Bắt biến trong vòng lặp for/foreach (lại nữa)

Đau đầu kinh điển: nếu bạn code logic ở phỏng vấn hoặc dự án lớn, luôn kiểm tra: có đang bắt biến “sống” từ vòng lặp không, và nó sẽ behave thế nào.

1
Khảo sát/đố vui
, cấp độ , bài học
Không có sẵn
Tuple và hàm cục bộ
Ưu điểm của tuple so với out và anonymous type
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION