Extension method (phương thức mở rộng) thêm một phương thức vào một kiểu có sẵn mà không cần sửa, kế thừa hay bọc kiểu đó. Bạn viết một phương thức static bình thường, đánh dấu tham số đầu tiên bằng this, và nơi gọi dùng nó với cú pháp dấu chấm như thể kiểu đó vốn đã có phương thức này:
Đầu ra:
Extension methods let yo...
12
short
Extension...
title.Truncate(24) được biên dịch chính xác thành StringExtensions.Truncate(title, 24). Cú pháp dấu chấm chỉ để tiện: phương thức vẫn là static, vẫn nằm trong StringExtensions và chỉ dùng được các thành viên public của string.
Quy tắc khai báo
- Phương thức phải là
staticvà nằm trong một lớpstatic, không generic và không lồng trong lớp khác. Lớp không static hoặc generic sẽ gây lỗi CS1106,Extension method must be defined in a non-generic static class; lớp lồng gây lỗi CS1109,Extension methods must be defined in a top level static class. thischỉ đặt ở tham số đầu tiên, và kiểu của tham số đó là kiểu được mở rộng. Các tham số tiếp theo là tham số bình thường.- Bản thân phương thức có thể là generic (
this IEnumerable<T> source), dù lớp thì không được. - Nó chỉ thấy những gì mọi code bên ngoài thấy: các thành viên public (và internal, trong cùng assembly). Trường private vẫn là private.
Quy ước là đặt tên lớp theo kiểu được mở rộng (StringExtensions, EnumerableExtensions) và gom chúng vào một namespace như MyApp.Extensions.
Mở rộng interface và IEnumerable<T>
Mở rộng một interface sẽ thêm phương thức cho mọi kiểu cài đặt interface đó. Vì vậy, mở rộng IEnumerable<T> sẽ đồng thời trao phương thức cho mảng, list, set, tập khóa của từ điển và kết quả truy vấn LINQ:
Đầu ra:
9.5
50
65
f0 f3 f6
EveryNth là generic: T được suy ra từ receiver, nên ở đây nó chạy với chuỗi và cũng chạy với mọi kiểu phần tử khác. Vì dùng yield return, nó lười (lazy) giống các toán tử LINQ có sẵn và có thể nối chuỗi với chúng.
LINQ được xây dựng trên extension method
Where, Select, OrderBy, Sum, First và phần còn lại của LINQ là các extension method trên IEnumerable<T>, được định nghĩa trong lớp static System.Linq.Enumerable. Đó là lý do using System.Linq; phải có ở đầu tệp thì list.Where(...) mới biên dịch được, và là lý do LINQ chạy được với mọi kiểu collection mà không kiểu nào phải tự cài đặt phương thức Where:
Đầu ra:
72, 88, 95
72, 88, 95
Hai dòng này là cùng một code. Cú pháp extension biến các lời gọi lồng nhau, vốn phải đọc từ trong ra ngoài, thành một chuỗi đọc theo đúng thứ tự các bước diễn ra. Đây là lý do chính extension method ra đời: chúng được thêm vào C# 3 cùng với LINQ. Trang LINQ trình bày các toán tử.
Phải import namespace
Extension method chỉ nằm trong phạm vi khi namespace của lớp static chứa nó được import bằng using. Không có using, lời gọi thất bại như thể phương thức không tồn tại:
error CS1061: 'string' does not contain a definition for 'Truncate' and no accessible extension method 'Truncate' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?)
Thêm using cho namespace chứa các extension (hoặc, từ C# 10, một global using duy nhất cho cả dự án):
Đầu ra:
EUR 49.90
Mặt ngược lại lại có ích: một extension method bạn không muốn xuất hiện ở khắp nơi có thể nằm trong một namespace mà chỉ vài tệp import.
Phương thức instance được ưu tiên
Khi một kiểu đã có sẵn phương thức instance phù hợp, trình biên dịch dùng nó và không bao giờ xét đến extension. Một extension cùng chữ ký là code chết ở mọi chỗ gọi dùng cú pháp dấu chấm:
Đầu ra:
Invoice (instance method): 120
Invoice (extension): 120 USD
extension: only reachable as a static call
Extension một đối số đã thua phương thức instance; extension hai đối số được dùng vì không có phương thức instance nào nhận một chuỗi. Điều này cũng có nghĩa là một bản cập nhật thư viện thêm phương thức instance trùng tên với extension của bạn sẽ âm thầm chuyển các lời gọi của bạn sang phiên bản của thư viện. Hãy chọn tên extension khó bị trùng.
Gọi trên null
Vì lời gọi thực chất là lời gọi static, receiver null không ném ngoại lệ tại chỗ gọi. Phương thức nhận null làm đối số đầu tiên và tự quyết định điều đó có nghĩa gì:
Đầu ra:
True
AL
ArgumentNullException: fullName
missing.IsBlank() trả về true thì tiện, nhưng có thể khiến người đọc bất ngờ vì họ nghĩ gọi thành viên trên null sẽ ném ngoại lệ. Chỉ chấp nhận null ở những phương thức có tên nói rõ điều đó (IsBlank, OrEmpty); ở những chỗ khác, hãy ném ArgumentNullException như LINQ.
Mở rộng enum
Enum không thể có phương thức riêng, nên đây là đối tượng rất hợp để mở rộng:
enum OrderStatus { Pending, Paid, Shipped, Delivered, Cancelled }
static class OrderStatusExtensions
{
public static bool IsFinal(this OrderStatus s) =>
s == OrderStatus.Delivered || s == OrderStatus.Cancelled;
}
// usage: if (order.Status.IsFinal()) { ... }
Extension member trong C# 14
Đến trước C# 14, chỉ phương thức mới làm extension được. C# 14 bổ sung khối extension, gom các thành viên cho một receiver và cho phép cả extension property lẫn thành viên static:
// C# 14 and later
public static class StringExtensions
{
extension(string s)
{
public bool IsBlank => string.IsNullOrWhiteSpace(s); // extension property
public string Truncate(int max) => s.Length <= max ? s : s[..max] + "...";
}
}
Extension method kiểu cổ điển với tham số this vẫn hợp lệ và là thứ gần như mọi code và thư viện hiện có đang dùng.
Khi nào nên viết extension method
Extension method phù hợp khi bạn không sở hữu kiểu đó (string, DateTime, interface của framework), khi bạn muốn cùng một helper cho mọi lớp cài đặt một interface, hoặc khi một chuỗi lời gọi dễ đọc hơn các lời gọi lồng nhau. Khi bạn sở hữu lớp, hãy thêm một phương thức instance thật: nó dùng được trạng thái private và nằm đúng chỗ người đọc tìm. Và tránh mở rộng object, vì việc đó đưa phương thức của bạn vào danh sách gợi ý của mọi giá trị trong chương trình.
Câu hỏi thường gặp
Extension method trong C# là gì?
Là một phương thức static có thể được gọi như thể nó là phương thức instance của một kiểu khác. Bạn viết nó trong một lớp static và đặt this trước tham số đầu tiên: public static bool IsBlank(this string s) => string.IsNullOrWhiteSpace(s);. Khi đó name.IsBlank() dùng được với mọi chuỗi mà không cần sửa hay kế thừa string.
Vì sao không tìm thấy extension method của tôi?
Extension method chỉ hiện ra khi namespace của nó được import. Nếu lớp static nằm trong MyApp.Extensions, hãy thêm using MyApp.Extensions; vào tệp gọi nó; nếu không, trình biên dịch báo CS1061, cho biết kiểu đó không có định nghĩa và không có extension method nào truy cập được mang tên đó. Kiểm tra thêm rằng lớp là static, không generic và không lồng trong lớp khác.
Extension method có ghi đè được phương thức instance không?
Không. Trình biên dịch tìm phương thức instance phù hợp trước, và chỉ xét đến extension method khi không có phương thức nào như vậy. Một extension trùng tên và tham số với phương thức instance sẽ bị âm thầm bỏ qua ở mọi chỗ gọi dùng cú pháp instance.
Có gọi extension method trên null được không trong C#?
Có. Lời gọi được biên dịch thành một lời gọi phương thức static với null là đối số đầu tiên, nên bản thân lời gọi không ném NullReferenceException. Phương thức tự quyết định phải làm gì: coi null là đầu vào hợp lệ (như một helper kiểu IsNullOrEmpty) hoặc ném ArgumentNullException, như các phương thức của LINQ.
C# có extension property không?
Trước C# 14 thì không. Đến lúc đó, chỉ phương thức mới làm extension được, nên một thứ lẽ ra là thuộc tính phải viết thành phương thức (GetFullName()). C# 14 bổ sung extension block, cho phép khai báo extension property và thành viên static bên cạnh phương thức.