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 prefix và message đề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
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
}
Ở đâ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
Ở đâ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.
GO TO FULL VERSION