Extension Methods


Extension method (phương thức mở rộng) cho phép bạn “viết thêm” các phương thức vào một kiểu có sẵn mà không cần tạo các lớp thừa kế, dịch lại hay sửa đổi kiểu dữ liệu sẵn có. Phương thức mở rộng là một dạng đặc biệt của phương thức tĩnh (static), nhưng chúng được gọi giống như là một phương thức thông thường trên kiểu được mở rộng. Với mã lệnh để gọi các phương thức này, khi được viết bằng C# và VB, không có gì khác nhau giữa việc gọi một extension method và các phương thức thực sự được định nghĩa trong kiểu dữ liệu đó.
Các extension method phổ biến nhất là các toán tử truy vấn LINQ chuẩn được thêm vào các kiểu System.Collections.IEnumerableSystem.Collections.Generic.IEnumerable<T>. Để dùng các toán tử truy vấn chuẩn này, trước tiên bạn phải using System.Linq, khi đó bất kỳ kiểu dữ liệu nào thừa kế từ IEnumerable<T> đều sẽ xuất hiện thêm các phương thức GroupBy, OrderBy, Average… Bạn có thể thấy các extension method đó nhờ tính năng IntelliSense liệt kê ra các phương thức ngay khi bạn gõ dấu chấm (.) ngay sau một thể hiện của  IEnumerable<T> như List<T> or Array.
Ví dụ sau đây sẽ cho thấy cách dùng phương thức truy vấn chuẩn OrderBy trên một mảng các số nguyên. Phần trong cặp dấu ngoặc ( và ) là một biểu thức lambda. Nhiều toán tử truy vấn chuẩn nhận tham số là các biểu thức lambda, nhưng đó không phải bắt buộc đối với các extension method. Để có thêm thông tin, xin hãy xem phần Biểu thức Lambda.
class ExtensionMethods2 
{

 static void Main()
 { 
 int[] ints = { 10, 45, 15, 39, 21, 26 };
 var result = ints.OrderBy(g => g);
 foreach (var i in result)
 {
 System.Console.Write(i + " ");
 } 
 } 
}
//Output: 10 15 21 26 39 45
Các extension method được định nghĩa như các phương thức tĩnh nhưng chúng được gọi bằng cách dùng cú pháp gọi phương thức thông thường. Tham số đầu tiên chỉ ra kiểu dữ liệu mà phương thức sẽ làm việc, và tham số này được khai báo với từ khóa this. Extension method chỉ có thể dùng được nếu bạn khai báo rõ namespace chứa nó bằng cách dùng using.
namespace ExtensionMethods
{
 public static class MyExtensions
 {
 public static int WordCount(this String str)
 {
 return str.Split(new char[] { ' ', '.', '?' }, 
 StringSplitOptions.RemoveEmptyEntries).Length;
 }
 } 
}

Để dùng được PTMR WordCount bạn phải khai báo:

using ExtensionMethods;

Và nó có thể được gọi sử dụng cú pháp sau:

string s = "Hello Extension Methods";
int i = s.WordCount();

Bạn gọi extension method với cú pháp của một instance method. Tuy nhiên đoạn lệnh IL được tạo ra bởi trình dịch sẽ diễn dịch đoạn lệnh gọi của bạn thành một lời gọi đến phương thức tĩnh. Do vậy, khái niệm về đóng gói (encapsulation) không bị xâm phạm, và thực sự, các extension method cũng không thể truy cập đến các biến private bên trong kiểu dữ liệu chúng mở rộng.
Để có thêm thông tin, xin đọc How to: Implement and Call a Custom Extension Method (C# Programming Guide).

Nói chung, bạn có lẽ sẽ gọi đến các extension method nhiều hơn nhiều so với việc viết ra chúng. Bởi vì các extension method được gọi dùng cú pháp như cách gọi các instance method, nên không cần kiến thức gì đặc biệt để sử dụng chúng. Để cho phép các extension method cho một kiểu nào đó, chỉ cần thêm vào một khai báo using cho namespace trong các phương thức được định nghĩa. Ví dụ, để dùng các toán tử truy vấn chuẩn, thêm khai báo sau vào chương trình của bạn:

using System.Linq;
(Bạn có thể phải thêm tham chiếu (reference) đến (System.Core.dll). Bạn sẽ thấy các toán tử truy vấn xuất hiện thêm trong IntelliSense khi dùng hầu hết các kiểu thừa kế từ IEnumerable<T>.
Ghi chú: Bạn vẫn có thể dùng các toán tử truy vấn chuẩn với String, dù có thể nó không xuất hiện trong IntelliSense.

Binding các phương thức mở rộng lúc biên dịch

Bạn có thể dùng các extension method để mở rộng một class hay một interface, nhưng không thể override chúng, nếu bạn viết một phương thức mở rộng trùng tên và tham số với một phương thức có trong lớp được mở rộng, chúng sẽ không bao giờ được gọi. Khi biên dịch, các extension method có độ ưu tiên thấp hơn các phương thức có trong kiểu dữ liệu được mở rộng. Nói cách khác, nếu một kiểu có phương thức tên Process(int i), và bạn có một extension method được khai báo giống hệt, trình dịch của bạn sẽ luôn dùng phương thức có sẵn trong kiểu. Khi trình dịch gặp một lời gọi phương thức, chúng sẽ luôn tìm trong các phương thức của lớp trước, nếu không thấy, chúng mới bắt đầu tìm các phương thức mở rộng, và gắn lời gọi vào nếu thấy. Ví dụ sau biểu diễn việc trình dịch xác định extension method hay instance method để gắn lời gọi vào.

Ví dụ:

Ví dụ sau biểu diễn các quy tắc trình dịch C# sử dụng để xác định xem liệu một lời gọi sẽ gọi đến instance method hay extension method. Lớp Extensions được khai báo là static và chứa các extension method cho bất kỳ kiểu nào thừa kế IMyInterface.Phương thức mở rộng MethodB không bao giờ được gọi vì tên và các thuộc tính của nó trùng hoàn toàn với các phương đã được định nghĩa trong interface.

Khi trình dịch không thể tìm thấy phương thức thuộc lớp nào khớp, nó sẽ tìm đến các phương thức mở rộng để sử dụng.

 
// Define an interface named IMyInterface.
namespace DefineIMyInterface
{
    using System;

    public interface IMyInterface
    {
        // Any class that implements IMyInterface must define a method
        // that matches the following signature.
        void MethodB();
    }
}

// Define extension methods for IMyInterface.
namespace Extensions
{
    using System;
    using DefineIMyInterface;

    // The following extension methods can be accessed by instances of any
    // class that implements IMyInterface.
    public static class Extension
    {
        public static void MethodA(this IMyInterface myInterface, int i)
        {
            Console.WriteLine
                ("Extension.MethodA(this IMyInterface myInterface, int i)");
        }

        public static void MethodA(this IMyInterface myInterface, string s)
        {
            Console.WriteLine
                ("Extension.MethodA(this IMyInterface myInterface, string s)");
        }

        // This method is never called in ExtensionMethodsDemo1, because each
        // of the three classes A, B, and C implements a method named MethodB
        // that has a matching signature.
        public static void MethodB(this IMyInterface myInterface)
        {
            Console.WriteLine
                ("Extension.MethodB(this IMyInterface myInterface)");
        }
    }
}

// Define three classes that implement IMyInterface, and then use them to test
// the extension methods.
namespace ExtensionMethodsDemo1
{
    using System;
    using Extensions;
    using DefineIMyInterface;

    class A : IMyInterface
    {
        public void MethodB() { Console.WriteLine("A.MethodB()"); }
    }

    class B : IMyInterface
    {
        public void MethodB() { Console.WriteLine("B.MethodB()"); }
        public void MethodA(int i) { Console.WriteLine("B.MethodA(int i)"); }
    }

    class C : IMyInterface
    {
        public void MethodB() { Console.WriteLine("C.MethodB()"); }
        public void MethodA(object obj)
        {
            Console.WriteLine("C.MethodA(object obj)");
        }
    }

    class ExtMethodDemo
    {
        static void Main(string[] args)
        {
            // Declare an instance of class A, class B, and class C.
            A a = new A();
            B b = new B();
            C c = new C();

            // For a, b, and c, call the following methods:
            //      -- MethodA with an int argument
            //      -- MethodA with a string argument
            //      -- MethodB with no argument.

            // A contains no MethodA, so each call to MethodA resolves to
            // the extension method that has a matching signature.
            a.MethodA(1);           // Extension.MethodA(object, int)
            a.MethodA("hello");     // Extension.MethodA(object, string)

            // A has a method that matches the signature of the following call
            // to MethodB.
            a.MethodB();            // A.MethodB()

            // B has methods that match the signatures of the following
            // nethod calls.
            b.MethodA(1);           // B.MethodA(int)
            b.MethodB();            // B.MethodB()

            // B has no matching method for the following call, but
            // class Extension does.
            b.MethodA("hello");     // Extension.MethodA(object, string)

            // C contains an instance method that matches each of the following
            // method calls.
            c.MethodA(1);           // C.MethodA(object)
            c.MethodA("hello");     // C.MethodA(object)
            c.MethodB();            // C.MethodB()
        }
    }
}
/* Output:
    Extension.MethodA(this IMyInterface myInterface, int i)
    Extension.MethodA(this IMyInterface myInterface, string s)
    A.MethodB()
    B.MethodA(int i)
    B.MethodB()
    Extension.MethodA(this IMyInterface myInterface, string s)
    C.MethodA(object obj)
    C.MethodA(object obj)
    C.MethodB()
 */

Hướng dẫn chung

Nói chung, theo chúng tôi bạn không nên viết nhiều extension method, và chỉ viết khi thực sự cần. Bất cứ khi nào có thể, bạn nên mở rộng một lớp bằng cách thừa kế lại nó. Để xem thêm thông tin, xin truy cập vào Inheritance (C# Programming Guide).
Khi dùng một extension method để mở rộng một lớp vì bạn không thể thay đổi mã nguồn, bạn sẽ chịu rủi ro là nếu lớp đó thay đổi, có thể phương thức của bạn sẽ không còn dùng được.
Nếu bạn viết extension method cho một kiểu nào đó, hãy nhớ hai điểm sau:
  • Một phương thức mở rộng sẽ không bao giờ được gọi nếu nó được khai báo tên và tham số trùng hoàn toàn với một phương thức có trong kiểu dữ liệu đó.
  • Các phương thức mở rộng sẽ được tìm kiếm ở cấp độ namespace. Ví dụ, nếu bạn có nhiều lớp static chứa các phương thức mở rộng bên trong namespace có tên Extensions, tất cả chúng sẽ trở nên dùng được ngay khi bạn sử dụng câu lệnh using Extensions;

Dịch từ http://msdn.microsoft.com/en-us/library/bb383977.aspx

3 thoughts on “Extension Methods

  1. Bài viết hữu ích quá, em cảm ơn anh rất nhiều ạ

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s