Có một số cạm bẫy đối với các nhà xây dựng tĩnh. Ví dụ: nếu một hàm dựng tĩnh throws an exception, bạn sẽ tiếp tục nhận được TypeInitializationException
bất cứ khi nào bạn truy cập bất kỳ thành viên nào của nó.
Nếu một nhà xây dựng tĩnh ném một ngoại lệ, thời gian chạy sẽ không gọi nó lần thứ hai và loại sẽ vẫn chưa được khởi tạo trong suốt thời gian của miền ứng dụng mà chương trình của bạn đang chạy.
Nói chung, các lớp tĩnh chỉ nên được sử dụng trong các tình huống không trạng thái mà bạn sẽ không cần bất kỳ khởi tạo nào. Nếu lớp học của bạn cần phải được khởi tạo, bạn có thể được tốt hơn bằng cách sử dụng singleton pattern, có thể lazily initialized về tiếp cận đầu tiên:
public class MyClass
{
private static readonly Lazy<MyClass> current =
new Lazy<MyClass>(() => new MyClass());
public static MyClass Current
{
get { return current.Value; }
}
private MyClass()
{
// Initialization goes here.
}
public void Foo()
{
// ...
}
public void Bar()
{
// ...
}
}
static void Main(string[] args)
{
MyClass.Current.Foo(); // Initialization only performed here.
MyClass.Current.Bar();
MyClass.Current.Foo();
}
Sửa: Tôi đã làm một số đọc thêm lên về vấn đề này, và dường như static constructors do gây deadlocks nếu bạn thực hiện các hoạt động chặn (ví dụ như không đồng bộ gọi lại hoặc đồng bộ hóa thread) bên trong chúng.
CLR sử dụng nội bộ khóa để ngăn khởi tạo kiểu (các hàm tạo tĩnh) được thực hiện nhiều lần đồng thời. Vì vậy, nếu constructor tĩnh của bạn cố gắng truy cập vào một thành viên khác của kiểu khai báo của nó từ một luồng khác, nó chắc chắn sẽ bế tắc. Vì “một thành viên khác” có thể là một hàm ẩn danh được khai báo như là một phần của một hoạt động PLINQ hoặc TPL, các lỗi này có thể tinh tế và khó xác định.
Igor Ostrovsky (MSFT) giải thích điều này trong bài viết Static constructor deadlocks của mình, cung cấp các ví dụ sau đây của một bế tắc:
using System.Threading;
class MyClass
{
static void Main() { /* Won’t run... the static constructor deadlocks */ }
static MyClass()
{
Thread thread = new Thread(arg => { });
thread.Start();
thread.Join();
}
}
Trong ví dụ trên, các chủ đề mới cần phải truy cập vào các chức năng ẩn danh rỗng, { }
, được xác định như gọi lại của nó. Tuy nhiên, vì chức năng ẩn danh được biên dịch như một phương pháp riêng khác của MyClass
đằng sau hậu trường, chuỗi mới không thể truy cập nó trước khi loại bắt đầu loại MyClass
. Và, vì hàm xây dựng tĩnh MyClass
cần phải đợi luồng mới hoàn thành trước (vì thread.Join()
), một bế tắc xảy ra sau đó.
Có điều gì khác ngoài ngoại lệ được ném bên trong hàm tạo không? Ví dụ, những gì có thể giải thích "bế tắc" như kịch bản tôi đang gặp phải? Có khóa nào liên quan đến các loại tĩnh bằng cách nào đó đằng sau hậu trường không? –
@liortal: Đã trả lời ở trên. – Douglas
Có sự khác biệt nào giữa những gì bạn có trong ví dụ đầu tiên và các dòng này không? 'riêng tĩnh chỉ đọc Lazy hiện tại; tĩnh MyClass {current = new Lazy (() => new MyClass()); } ' (xin lỗi dường như không thể định dạng đúng :)) –
Mark