Menu

Từ khóa static trong C#: lớp, phương thức và field tĩnh

static nghĩa là gì trong C#: thành viên thuộc về kiểu thay vì một đối tượng, trạng thái dùng chung giữa các instance, phương thức tĩnh và lớp tĩnh, hàm khởi tạo tĩnh, const và static readonly, using static, và lỗi CS0120.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

static đánh dấu một thành viên thuộc về kiểu, không thuộc về đối tượng. Field của instance tồn tại một bản cho mỗi đối tượng; field tĩnh chỉ tồn tại đúng một bản. Phương thức của instance chạy trên một đối tượng (order.Ship()); phương thức tĩnh chạy trên kiểu (Math.Round(x)) và hoàn toàn không cần đối tượng.

Field tĩnh: một bản, dùng chung

Cách rõ nhất để thấy sự khác biệt là một bộ đếm. Mỗi Order có Id riêng, nhưng số đơn hàng đã tạo đến giờ phải nằm ở một nơi dùng chung.

Đầu ra:

Ana #1, Ben #2, Chen #3
Orders created: 3

Thành viên tĩnh được truy cập qua tên kiểu: Order.Created, không phải a.Created. C# không cho phép truy cập thành viên tĩnh qua một instance (lỗi CS0176), nhờ đó ở nơi gọi luôn thấy rõ giá trị này là dùng chung.

Phương thức tĩnh

Phương thức tĩnh không có this. Nó chỉ làm việc với tham số của mình và với các thành viên tĩnh khác. Vì vậy đây là dạng tự nhiên cho những hàm tiện ích tính toán từ đầu vào:

Đầu ra:

61.38 EUR
7

Bản thân Main cũng là tĩnh: runtime gọi nó trước khi có bất kỳ đối tượng nào.

Lỗi CS0120: code tĩnh dùng thành viên của instance

Vì Main là tĩnh, người mới học thường gặp lỗi này ngay khi thêm phương thức thứ hai:

class Program
{
    int visits;
    void Greet() { Console.WriteLine("Hello"); }

    static void Main()
    {
        Greet();    // error CS0120: An object reference is required for the non-static field, method, or property 'Program.Greet()'
        visits++;   // error CS0120: An object reference is required for the non-static field, method, or property 'Program.visits'
    }
}

Phương thức tĩnh không có đối tượng, nên không có visits nào để nó tăng lên. Có hai cách sửa, và cách đúng tùy vào bản chất của thành viên đó:

  • Thành viên đó thực sự là dữ liệu riêng của từng đối tượng: tạo một đối tượng và dùng nó. var app = new Program(); app.Greet();
  • Thành viên đó không cần trạng thái đối tượng: đánh dấu nó là static. static void Greet() { ... }

Đánh dấu mọi thứ là static để lỗi biến mất thì được với chương trình nhỏ xíu, nhưng sẽ thành trạng thái toàn cục trong chương trình thật. Hãy quyết định bằng cách tự hỏi dữ liệu đó thuộc về một đối tượng hay thuộc về cả chương trình.

Chiều ngược lại luôn được phép: phương thức của instance có thể thoải mái gọi thành viên tĩnh.

Lớp tĩnh

Lớp được khai báo static chỉ chứa được thành viên tĩnh và không bao giờ tạo instance được. Nó là một nhóm hàm và hằng số có tên.

Đầu ra:

77
100
True

Trình biên dịch buộc phải tuân theo quy ước này: thành viên instance bên trong lớp tĩnh là lỗi CS0708, và dùng new với nó là lỗi CS0712. Math, Console, File, Path và Enumerable đều là lớp tĩnh. Extension method cũng phải nằm trong một lớp tĩnh.

Hàm khởi tạo tĩnh và static readonly

Field tĩnh cần nhiều hơn một biểu thức để có giá trị có thể được gán trong hàm khởi tạo tĩnh (static constructor), được runtime chạy đúng một lần, trước khi kiểu được dùng lần đầu:

Đầu ra:

Before first use
ShippingRates initialized
5.50
3
2026-01-01

readonly nghĩa là field chỉ được gán trong phần khởi tạo của nó hoặc trong hàm khởi tạo tĩnh. Sau đó không thể gán lại, dù đối tượng mà nó trỏ tới vẫn có thể thay đổi được, đó là lý do từ điển được công khai dưới dạng IReadOnlyDictionary.

const và static readonly

Cả hai đều cho bạn một giá trị có tên dùng chung cho cả chương trình, và khác nhau ở thời điểm giá trị được cố định:

conststatic readonly
Giá trị cố định lúcbiên dịchchạy, một lần
Kiểu được phépsố, char, bool, string, enum, nullmọi kiểu
Ngầm định là tĩnhcó (không được viết static const)phải khai báo static tường minh
Được chép vào assembly gọi nócókhông, đọc từ field
Dùng được trong case của switch và đối số attributecókhông

Hàng về việc sao chép quan trọng với thư viện: nếu assembly A khai báo public const int MaxItems = 50; và assembly B dùng nó, B sẽ chứa literal 50. Đổi A thành 100 không ảnh hưởng gì đến B cho đến khi B được biên dịch lại. Với giá trị có thể thay đổi giữa các phiên bản, hãy ưu tiên static readonly.

using static

Chỉ thị using static (C# 6) nhập các thành viên tĩnh của một kiểu, để bạn gọi chúng mà không cần tên kiểu:

Đầu ra:

Area: 28.27
40

Cách này đọc rất gọn với code nhiều phép toán. Ở chỗ khác thì nên dùng hạn chế: một lời gọi WriteLine hay Parse đứng trơ trọi sẽ che mất phương thức đến từ kiểu nào.

Trạng thái tĩnh và luồng

Field tĩnh được dùng chung bởi mọi luồng trong tiến trình. Tăng nó từ nhiều luồng cùng lúc sẽ làm mất cập nhật, vì created++ gồm một lần đọc, một phép cộng và một lần ghi, và hai luồng có thể xen kẽ chúng. Hãy dùng Interlocked.Increment(ref created) cho bộ đếm, hoặc lock cho những gì lớn hơn. Ứng dụng web là nơi lỗi này hay xảy ra nhất: mỗi request chạy trên một luồng nào đó, nên field tĩnh chứa "người dùng hiện tại" sẽ bị mọi người dùng dùng chung.

Một điểm tinh tế nữa: trong lớp generic, mỗi kiểu được tạo ra có field tĩnh riêng. Cache<int>.Count và Cache<string>.Count là hai field khác nhau.

Khi nào dùng static

Nên dùng:

  • Hàm tiện ích thuần tính kết quả từ đối số: định dạng, chuyển đổi, kiểm tra dữ liệu.
  • Hằng số và dữ liệu tra cứu chỉ đọc dùng chung cho cả chương trình.
  • Factory method, như TimeSpan.FromMinutes(5) hoặc Temperature.FromCelsius(21) của riêng bạn.
  • Extension method.

Không nên dùng:

  • Trạng thái toàn cục có thể thay đổi (người dùng hiện tại, giỏ hàng, cài đặt hay thay đổi). Nó ràng buộc mọi đoạn code chạm vào nó và khiến các bài kiểm thử phụ thuộc vào phần còn sót lại của nhau.
  • Bất cứ thứ gì bạn sẽ muốn thay thế khi kiểm thử, như đồng hồ, cơ sở dữ liệu hay dịch vụ gửi email. Lời gọi tĩnh không thể thay bằng bản giả; một instance được truyền vào hàm khởi tạo thì có thể.

Câu hỏi thường gặp

static nghĩa là gì trong C#?

Thành viên static thuộc về chính kiểu đó chứ không thuộc về đối tượng nào. Field tĩnh chỉ có một bản dù có bao nhiêu đối tượng đi nữa, và phương thức tĩnh được gọi qua tên kiểu (Math.Max(3, 7)) mà không cần tạo instance. Phương thức tĩnh không có this, nên không thể dùng trực tiếp các field của instance.

Lớp static trong C# là gì?

Lớp được khai báo static chỉ chứa được thành viên tĩnh và không bao giờ tạo instance được (dùng new với nó là lỗi CS0712). Nó gom các hàm liên quan không cần trạng thái đối tượng, như Math, Console hay File. Extension method bắt buộc phải khai báo trong một lớp tĩnh.

Làm sao sửa lỗi "An object reference is required for the non-static field, method, or property"?

Đó là lỗi CS0120: một phương thức tĩnh, thường là Main, đang dùng thành viên của instance. Hoặc tạo một đối tượng rồi gọi thành viên trên nó (var app = new Program(); app.Run();), hoặc đánh dấu thành viên đó là static nếu nó không cần dữ liệu riêng của từng đối tượng.

const và static readonly trong C# khác nhau thế nào?

const là hằng số lúc biên dịch (số, chuỗi, bool, enum) mà giá trị được chép vào mọi assembly sử dụng nó. Field static readonly được gán một lần lúc chạy, trong phần khởi tạo hoặc trong hàm khởi tạo tĩnh, và có thể chứa bất kỳ kiểu nào, như DateTime hay một list. Dùng const cho giá trị không bao giờ đổi, static readonly cho mọi trường hợp còn lại.

Khi nào nên dùng static trong C#?

Dùng static cho code chỉ làm việc với tham số của nó (các hàm tiện ích thuần như hàm định dạng giá tiền), cho hằng số và bộ nhớ đệm thực sự toàn cục với chương trình, và cho factory method. Tránh static cho bất cứ thứ gì giữ trạng thái theo từng người dùng hay từng request, hoặc thứ bạn sẽ muốn thay thế khi kiểm thử: trạng thái dùng chung có thể thay đổi rất khó kiểm thử và không an toàn giữa các luồng nếu không có khóa.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu