程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> .NET實例教程 >> C# 3.0 之 擴展方法 (Extension Methods)

C# 3.0 之 擴展方法 (Extension Methods)

編輯:.NET實例教程

在以往的編程模式裡,一旦一個類型(可以是類、接口、結構、枚舉或者委托等等)定義完畢並編譯完成後,它就基本上被確定了。唯一的修改方法就是打開代碼,更新之後重新編譯。當然現在有些很好很強大的方法比如使用反射(System.Reflection.Emit),但C# 3.0有一種叫做“擴展方法”(extension method)的功能。即保持現有Type原封不動的情況下對其進行擴展,用戶可以在對Type的定義不做任何變動的情況下,為之添加所需的方法成員。

    通過這種方法,我們可以將新功能“注入”到已有的程序集中,而我們並不需要擁有那個程序集的源代碼,也可以用來實現在不修改原始類型聲明的條件下,強迫一個類型來支持一組成員(在多態性中很有用)。

    定義擴展方法的第一個限制,就是他們必須被定義在靜態類中,並且每個擴展方法也必須被聲明為靜態的;第二個限制,所有的擴展方法的第一個參數都使用this關鍵字;第三個限制,無論是直接訪問內存中的實例還是靜態地通過靜態類的定義,都可以調用任意一個擴展方法。

    呃,其實我看到這裡也有些暈,還是用實例來說話吧 :-)

定義擴展方法

    下面是一個叫做MyExtensions的類,定義了兩個擴展方法。第一個方法使用了System.Reflection命名空間,可以使得.Net基類庫的所有對象有一個DisplayDefiningAssembly(),第二個方法是個翻轉int型數據的函數,比如說可以把124變成421。

    static class MyExtensions

    {

        public static void DisplayDefiningAssembly(this object obj)

        {

            // 該方法允許任意object顯示它被定義的程序集

            Console.WriteLine("{0} lives here:\n\t->{1}\n", obj.GetType().Name, Assembly.GetAssembly(obj.GetType()));

        }

 

        public static int ReverseDigits(this int i)

        {

            char[] digits = i.ToString().ToCharArray(); // 先轉換為string,再存到數組中

 

            Array.Reverse(digits);  // 逆序排列

            string newDigits = new string(digits);

 

            return int.Parse(newDigits); // 以int類型返回

        }

 

        public static void Foo(this int i)

        {

            //Int32 類型擁有的Foo()方法

            Console.WriteLine("{0} rings the bell.", i);

        }

 

        public static void Foo(this int i, string message)

        {

            //重載的 Foo() 方法

            Console.WriteLine("{0} rings the bell and said: {1}", i, message);

        }

    }

 

 

    可以看到所有方法的第一個參數具有一個this關鍵字。第一個方擴展的是System.Object,而第二個方法則只能擴展整型,如果一個非整型試圖調用這個方法,將會得到一個編譯時錯誤。一個擴展方法可以擁有多個參數,但只有第一個需要加this,這在後面被重載的方法裡可以看到。

 

    另外一個要注意的問題是,帶有擴展方法的類不能嵌套在另一個類中,它必須是頂層類。

 

從實例級別調用擴展方法

    好,現在有了這兩個擴展方法,我們來看一看任意一個對象(當然,必須是在.Net基類庫中的)是怎麼執行DisplayDefiningAssembly()的,而一個System.Int32類型是如何執行ReverseDigits()和Foo()的。

   static void Main(string[] args)

   {

       // int 類型被指定了一個新方法

       int myInt = 12345678;

       myInt.DisplayDefiningAssembly();

 

       // DataSet也是

       System.Data.DataSet ds = new System.Data.DataSet();

       ds.DisplayDefiningAssembly();

 

       // SoundPlayer也一樣

       System.Media.SoundPlayer sp = new System.Media.SoundPlayer();

       sp.DisplayDefiningAssembly();

 

       // 為 int 類型新加的功能

       Console.WriteLine("Value of myInt: {0}", myInt);

       Console.WriteLine("After reversed: {0}", myInt.ReverseDigits());

 

       // 測試重載的擴展方法

       myInt.Foo();

       myInt.Foo("This is the sample provided by SpadeQ!");

 

       // 下面這個就不行了

       bool b = true;

       // b.Foo();

Console.ReadLine();

   }

 

    將上面這個Main方法添加到上面的類中,作為程序的入口方法,然後執行,可以看到下面的結果:

 查看更多精彩圖片

 

 靜態地調用擴展方法

     回想一下,所有擴展方法的第一個參數都被添加了一個this關鍵字,如果我們想一想在這背後發生了些什麼(可以使用ildasm.exe或者Lutz Roeder's Reflector),我們將看到編譯器簡單地調用了normal靜態方法,將調用這些方法的變量當作一個參數來傳送。將上面Main方法中的語句替換如下:

  static void Main(string[] args)

  {

      int myInt = 12345678;

      MyExtensions.DisplayDefiningAssembly(myInt);

 

      System.Data.DataSet ds = new System.Data.DataSet();

      MyExtensions.DisplayDefiningAssembly(ds);

 

      System.Media.SoundPlayer sp = new System.Media.SoundPlayer();

      MyExtensions.DisplayDefiningAssembly(sp);

 

      Console.WriteLine("Value of myInt: {0}", myInt);

      Console.WriteLine("After reversed: {0}", MyExtensions.ReverseDigits(myInt));

 

      MyExtensions.Foo(myInt);

      MyExtensions.Foo(myInt, "This is the sample provided by SpadeQ!");

 

      Console.ReadLine();

  }

    運行結果仍然是一樣的。也就是說,從某個對象調用擴展方法,看起來像是實例級別的調用,其實是編譯器在忽悠我們而已~~~

 

生成以及使用擴展庫

 

    最後一個有關擴展方法的話題就是建立擴展庫。一切有用的東西都可以封裝到庫裡以供復用,這是現代編程的王道。為了演示這個過程,重新建立一個.Net Library工程,然後將代碼轉移進去,看起來應該如下所示:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Reflection;

 

namespace MyExtensionLibrary

{

    public static class MyExtensions

    {

   public static void DisplayDefiningAssembly(this object obj)

        {

            // 該方法允許任意object顯示它被定義的程序集

            Console.WriteLine("{0} lives here:\n\t->{1}\n", obj.GetType().Name, Assembly.GetAssembly(obj.GetType()));

        }

 

        public static int ReverseDigits(this int i)

        {

            char[] digits = i.ToString().ToCharArray(); // 先轉換為string,再存到數組中

 

            Array.Reverse(digits);  // 逆序排列

            string newDigits = new string(digits);

 

            return int.Parse(newDigits); // 以int類型返回

        }

    }

}

    注意,如果想在庫外調用這些方法,必須在類名前面加上public,因為C#默認的訪問級別是private。在這裡,還可以顯示指明擴展應用,即在public static ...前面加上一個[Extension],當然現在並不必須這麼做。

    編譯完成之後回到我們原來的那個工程,添加對這個新編譯出來的MyExtensionLibrary.dll的引用,修改代碼如下:

using System;

using MyExtensionLibrary;

 

namespace ConsoleApplication1

{

    class Program

    {

        static void Main(string[] args)

        {

            int myInt = 12345678;

            myInt.DisplayDefiningAssembly();

 

            System.Data.

DataSet ds = new System.Data.DataSet();

            ds.DisplayDefiningAssembly();

 

            System.Media.SoundPlayer sp = new System.Media.SoundPlayer();

            sp.DisplayDefiningAssembly();

 

            Console.WriteLine("Value of myInt: {0}", myInt);

            Console.WriteLine("After reversed: {0}", myInt.ReverseDigits());

 

            myInt.Foo();

            myInt.Foo("This sample is provided by SpadeQ");

 

            Console.ReadLine();

        }

    }

}

 

 

    可以得到同樣的結果,就像使用普通的方法一樣。所有object類型都已經被加上了DisplayDefiningAssembly()擴展,所有的int類型還被加上了ReverseDigits()擴展,前提是我們無需分拆出object和int的代碼,我們甚至連它的內部結構都不知道,但仍然可以向它們的數據類型“注入”我們自己想要的函數。 

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved