1. Giới thiệu
Ngày xưa, interface nghiêm ngặt như nội quy trường tư: chỉ có chữ ký hàm, không có trường, không có hiện thực, không có thành viên static! Nhưng .NET phát triển, còn ngôn ngữ lập trình thì như sinh vật sống: để đáp ứng thử thách mới, nó phải tiến hóa.
Khi các phiên bản mới của C# ra đời, interface học được nhiều trò mới. Một trong những thứ nổi bật nhất là thành viên static trong interface. Giờ đây, interface có thể chứa phương thức static, thuộc tính và sự kiện static. Trong các phiên bản mới của C# (từ C# 11 trở đi) còn có thể khai báo cả static abstract method, bắt buộc các kiểu thực thi interface này phải hiện thực.
Đây là một sự thay đổi lớn về tư duy, làm thay đổi cách tiếp cận lập trình tổng quát và hướng đối tượng.
Nói đơn giản: thành viên static của interface là thành viên "chung", truy cập qua chính kiểu interface (hoặc kiểu thực thi), chứ không phải qua instance của object.
Trước đây chỉ có class, struct và enum mới có thể có phương thức và thuộc tính static, nhưng giờ interface cũng có thể rồi.
Trông nó như thế nào? Ví dụ cú pháp
public interface IMyMath
{
static int Add(int a, int b) => a + b; // Phương thức static (có hiện thực mặc định)
static abstract int Multiply(int a, int b); // Bắt buộc kiểu thực thi phải hiện thực
}
- static — thành viên truy cập ở cấp độ kiểu, không phải instance.
- static abstract — contract "bắt buộc" phải hiện thực phương thức static ở kiểu thực thi.
- Trong interface (giống như class) giờ có thể khai báo phương thức, thuộc tính và sự kiện static. Cũng có thể tạo hằng số. Tuy nhiên interface vẫn không thể có trường instance hoặc trường static (trừ hằng số).
Chức năng của thành viên static trong interface là cho phép khai báo các "operation" chung. Ví dụ, nếu bạn có một collection các object và muốn gọi "so sánh" cho chúng mà không biết kiểu nào thực thi interface, bạn có thể làm điều này với static abstract member.
2. Phương thức static có hiện thực trong interface
Tại sao lại cần cái này?
Vấn đề kinh điển: bạn muốn mô tả không chỉ các phương thức "instance" (ví dụ, làm gì đó với object), mà còn cả "static" (ví dụ, tạo object mới từ chuỗi hoặc so sánh hai object theo kiểu static). Trước đây phải giải quyết bằng pattern (Factory, Comparer, Helper), nhưng giờ có thể thể hiện trực tiếp qua interface.
Điều này đặc biệt quan trọng cho Generics và các thuật toán làm việc với kiểu bất kỳ:
- Hiện thực toán tử tổng quát (ví dụ, cộng, so sánh).
- Ràng buộc code tổng quát: "Bất kỳ kiểu nào có phương thức hoặc toán tử static…"
- Serialization/deserialization: khi cần tạo object từ chuỗi mà không biết kiểu lúc viết code.
Phương thức static có thân hàm
Từ C# 8, interface cho phép phương thức static có thân hàm. Nó giống như static method trong class.
public interface IUtility
{
static void PrintHello()
{
Console.WriteLine("Xin chào từ Interface!");
}
}
Có thể gọi như sau: IUtility.PrintHello();
Rất tiện cho các hàm phụ trợ logic thuộc về interface, nhưng không gắn với hiện thực cụ thể. Ví dụ: thống kê cho tất cả object kiểu này, phương thức factory (CreateDefault), kiểm tra chung (ví dụ, kiểm tra giá trị hợp lệ).
Lưu ý: thành viên static của interface không "ghi đè" trong class
Nếu bạn khai báo trong interface static void Method() { ... }, thì class thực thi có thể khai báo static method cùng tên, cùng chữ ký — nhưng không phải ghi đè! Đó là hai phương thức độc lập — trùng tên thôi, chứ không phải "virtual static method".
3. Thành viên static abstract
Từ C# 11, interface cho phép khai báo static abstract method. Nghĩa là: "mọi class hoặc struct thực thi interface này phải khai báo thành viên static cùng chữ ký".
Ví dụ:
public interface IParsable<T>
{
static abstract T Parse(string s);
}
Bất kỳ kiểu nào thực thi interface này đều phải khai báo phương thức static Parse(string s).
Hiện thực interface này trên class
public class Temperature : IParsable<Temperature>
{
public int Value { get; set; }
// Hiện thực static!
public static Temperature Parse(string s)
{
var temp = new Temperature();
temp.Value = int.Parse(s);
return temp;
}
}
Nó hoạt động thế nào?
Điều này đặc biệt hay trong code tổng quát (Generics):
public static T ParseFromString<T>(string s) where T : IParsable<T>
{
return T.Parse(s);
}
// Sử dụng:
var temp = ParseFromString<Temperature>("42");
Giờ bạn có thể viết code thực sự tổng quát, làm việc với bất kỳ kiểu nào có "hành vi static"!
4. Thành viên static trong interface vs. static member trong class thông thường
| Đặc điểm | Static member của class | Static member của interface |
|---|---|---|
| Kế thừa | Không | Không, nhưng hiện thực theo contract interface |
| Bắt buộc hiện thực | Không | Chỉ khi static abstract |
| Dùng trong Generics | Không (trước C# 11) | Có (với static abstract) |
| Có thể có hiện thực mặc định | Có | Có |
| Ghi đè | Không | Không, chỉ bắt buộc hiện thực |
| Truy cập khi gọi | Qua tên kiểu | Qua kiểu interface hoặc kiểu thực thi |
7. Ví dụ thực tế
Xem thử cách cải tiến ứng dụng học tập nhỏ của chúng ta với các tính năng mới này.
Giả sử có interface "IPrintable":
public interface IPrintable
{
void Print();
static void PrintAll(IEnumerable<IPrintable> items)
{
foreach (var item in items)
{
item.Print();
}
}
}
Giờ có thể gọi tiện lợi:
var documents = new List<IPrintable>
{
new Invoice { Number = "INV-001" },
new Receipt { Number = "RC-007" }
};
IPrintable.PrintAll(documents); // phương thức static của interface!
Kiểu kiến trúc này rất hợp cho các thao tác "nhóm" trên mọi hiện thực của interface.
Ví dụ nâng cao: cộng tổng quát các kiểu số
Giả sử có interface:
public interface IAddable<T>
{
static abstract T Add(T left, T right);
}
Hiện thực cho số nguyên (class bọc):
public struct MyInt : IAddable<MyInt>
{
public int Value { get; }
public MyInt(int val) => Value = val;
public static MyInt Add(MyInt left, MyInt right) => new MyInt(left.Value + right.Value);
}
Và cuối cùng, hàm tổng quát để cộng hai số kiểu T:
public static T Sum<T>(T a, T b) where T : IAddable<T>
{
return T.Add(a, b);
}
// Sử dụng:
var x = new MyInt(5);
var y = new MyInt(6);
var z = Sum(x, y); // z.Value == 11
Chính vì sự tổng quát này mà support static member trong interface mới ra đời!
8. Lỗi thường gặp và lưu ý
Sống với tính năng mới không phải lúc nào cũng đơn giản như ví dụ. Đây là vài điểm có thể làm newbie bối rối:
Phương thức static của interface không "kế thừa" vào class thực thi. Nếu khai báo trong interface static void Foo(), thì MyClass.Foo() và IMyInterface.Foo() là hai phương thức hoàn toàn khác nhau.
Static abstract bắt buộc phải hiện thực. Nếu quên — compiler sẽ báo class chưa hiện thực đủ interface.
Generic constraints: để dùng static abstract member, phải ràng buộc generic theo interface (where T : IMyInterface).
Không phải công cụ nào cũng support tính năng mới. Ví dụ, Rider, VS Code hoặc Roslyn-analyzer cũ không phải lúc nào cũng hiển thị đúng static abstract member trong interface nếu .NET chưa support C# 11+.
Đừng nhầm với extension-method của interface: chúng được hiện thực riêng và không hoạt động như static member.
GO TO FULL VERSION