C#中的擴展方法及用途


GPS平台、網站建設、軟件開發、系統運維,找森大網絡科技!
http://cnsendnet.taobao.com
來自森大科技官方博客
http://www.cnsendblog.com/index.php/?p=476

 

擴展方法使你能夠向現有類型“添加”方法,而無需創建新的派生類型、重新編譯或以其他方式修改原始類型。 擴展方法是一種特殊的靜態方法,但可以像擴展類型上的實例方法一樣進行調用。 以上是msdn官網對擴展方法的描述,現在我通過一個情景例子來對此進行闡釋。假設一個控制台程序class Program{}里面的主函數如下:

 

static void Main(string[] args)

        {

            DateTime now = DateTime.Now;

            string time = now.ToString("yyyy-MM-dd HH:mm:ss");

            Console.WriteLine(time);

            Console.ReadKey();

        }

 

假設需求變了,日期的顯示格式要變成"yyyy-MM-dd"這種格式,當然只需要初始化time時按下面寫法改寫即可:

string time = now.ToString("yyyy-MM-dd");

但是如果要改變日期格式的有很多個類呢?每個都要改一次嗎?這樣一旦需求變來變去就忙死人了。傳統的解決方式是封裝一個幫助類,在里面寫方法,然后供其他類調用。

本例在當前項目模仿添加一個DateHelper類:public class DateHelper{},在類里面定義方法:

public static string DateToString(DateTime dt)

        {

            return dt.ToString("yyyy-MM-dd HH:mm:ss");

        }

於是原來的主函數改寫如下:

 

static void Main(string[] args)

        {

            DateTime now = DateTime.Now;

            string time = DateHelper.DateToString(now);

            Console.WriteLine(time);

            Console.ReadKey();

        }

 

此時如果變需求,只需要改寫DateHelp類里的DateToString()方法就行了,不管有多少個類調用此方法,都會被影響。問題解決了,可是這樣要調用另一個類的方法,還是有點麻煩,有沒有什么方法能夠讓我們像now.DateToString()一樣直接調用呢?當然DateTime是微軟寫好的,我們改不了,無法創建想要的實例方法,於是,便引出了擴展方法。
下面是擴展方法的要素:

1.此方法必須是一個靜態方法

2.此方法必須放在靜態類中

3.此方法的第一個參數必須以this開頭,並且指定此方法是擴展自哪個類型

根據以上要素,我們DateHelper類改成靜態類:public static class DateHelper{} ,同時改寫DateToString()方法:

public static string DateToString(this DateTime dt)

        {

            return dt.ToString("yyyy-MM-dd HH:mm:ss");

        }

此時回到主函數方法體,輸入"now."便可以看見自動提示有個DateToString()方法,於是代碼可以這樣寫:

 

static void Main(string[] args)

        {

            DateTime now = DateTime.Now;

            string time = now.DateToString();

            Console.WriteLine(time);

            Console.ReadKey();

        }

 

顯而易見,這樣用起來會更加便捷,而且這樣讓我們看起來確實就像是被擴展類型本身具有的實例方法一樣,可讀性很高。下面概括一下擴展方法的特點:

1.擴展方法擴展自哪個類型,就必須是此類型的變量來使用,其他類型無法使用,本例擴展自DateTime類型,就只能是被DateTime類型的變量.出來(now.DateToString())
2.擴展方法中的this后面的參數不屬於方法的參數,本例是無參數,this后面的DateTime dt是指明擴展方法擴展自何種類型
3.如果擴展方法和實例方法具有相同的簽名,則優先調用實例方法
4.擴展自父類上的方法,可以被子類的對象直接使用
5.擴展自接口上的方法,可以被實現類的對象直接使用
6.擴展方法最終還是被編譯器編譯成:靜態類.靜態方法(),本例中now.DateToString()最終還是會被編譯成DateHelper.DateToString(now),這是它的本質

實際上,我們可能會遇到這樣的情景,如在接口擴展一個方法的時候,所有的原本已實現該接口的類都要實現新擴展的方法,這樣的改動是一個很麻煩的工作,可以使用擴展方法“曲線救國”;而有時候我們想為某個類添加新方法卻不想改動這個類,那么擴展方法這種“偽添加”方法的方式就體現出它的價值了。最常見的擴展方法是LINQ標准查詢運算符,運用廣泛,這種方便快捷的方式理應博得碼農們點1024個贊。

 

什么是擴展方法?

  擴展方法從字面上理解是指擴展的方法,而對應到面向對象編程這個格局中則是指為一個類提供的擴展方法。按照我們通常的理解,我們首先需要獲得某個類的源代碼,然后在這個類代碼中增加成員方法,這樣就可以達到為一個類提供擴展方法的目的。可是不幸地是,這種方法在沒有源代碼的情況下就無法奏效了,而且我們人為地去改變源代碼有可能會破壞整個代碼的穩定性。那么有沒有一種方法能在不改變源代碼的前提下為某個類提供擴展方法呢?這就是我們今天要說的擴展方法,所以我們可以將擴展方法理解為在不改變源代碼的前提下向外部提供擴展方法的一種方式。C#中的擴展方法實現起來是相對來說比較簡單的,例如我們做在Unity3D游戲開發的時候,可能會用到DOTween這個插件。這個插件是iTween的作者重新編寫一個動畫插件,效率上比iTween有較大的提升。更為重要的一點是,它采用擴展方法這種實現方式,使得我們在調用這些API接口的時候難以感覺到我們是在使用一個插件,更像是在使用Unity3D的原生函數,所以當我們使用DOTween + uGUI 這樣的組合的時候,內心會感到無比的舒暢,一切都像是水到渠成一般。

擴展方法有哪些特點?

  擴展方法在實現上和普通的面向對象編程是一樣的,換句話說,我們只需要定義一個類,然后在里面添加並實現相應的方法即可。但是這里需要注意的地方有三點,第一,實現擴展方法的類必須是靜態類且類的名稱和實現擴展方法的類無關;第二、實現擴展方法的類方法必須是靜態方法;第三、實現擴展方法的類方法的第一個參數必須是使用this關鍵字指明要實現擴展方法的類。例如,我們知道將一個合法字符串類型轉換為整型,可以使用int.parse()方法,假如我們希望為string類型擴展一個ToInt方法應該怎么辦呢?我們一起來看下面的這段代碼:

  1. ///<summary>
  2. /// 1、定義一個靜態類
  3. /// 2、靜態類的名稱和要實現擴展方法的具體類無關
  4. ///</summary>
  5. public static class SomeClass
  6. {
  7.     ///<summary>
  8.     /// 3、實現一個具體的靜態方法
  9.     ///</summary>
  10. ///<param name="str">4、第一個參數必須使用this關鍵字指定要使用擴展方法的類型</param>
  11. ///<returns></returns>
  12. public static int ToInt(this string str)
  13. {
  14. return int.Parse(str);
  15. }

16.}

需要注意的是C#支持擴展方法是從.NET3.5版本開始,所以在編寫擴展方法的時候請確保你的.NET版本是否滿足這一要求。提到版本問題,有很多朋友尤其是從Unity5.0以后開始學習Unity3D的朋友,常常會在我的博客中留言提到我的代碼無法在新環境下運行等等類似地問題,我覺得這個世界上更新速度最快的當屬IT技術了,大家使用新版本沒有問題,可是有時候因為技術發展中的歷史遺留問題例如Python2.7和Python3、Unity4.X和Unity5.X,這個時候可能出現版本不兼容的問題,這個時候如果網絡上的資源沒有及時更新,建議大家還是及時查看官方的最新文檔,因為在博主看來網絡上的書籍或者相關文章都是用來參考的,古話說:盡信書不如無書,只有客觀、冷靜地判斷知識的正確與否,我們方能學到真正有用的知識。

  好了,現在我們編寫完這個擴展方法以后,就可以像下面這樣使用擴展方法了:

  1. string str = "1234";
  2. int val = str.ToInt();

這個示例向大家展示了如何編寫一個無參數的擴展方法,那么當我們需要在擴展方法中傳入參數的時候該怎么做呢?我們只需要在第一個參數后繼續加入參數的聲明就好了。例如我們在Unity3D中常常需要給一個3D物體設置坐標,通常我們可以通過下面的代碼來實現:

transform.position = new Vector3(1,1,1);

這個代碼到目前為止是比較簡潔的,可是我們知道在Unity3D中除了position屬性以外還有localPosition屬性,如果我們的代碼中再涉及坐標計算的話,我相信這個代碼一定會變得非常的長。更有甚者,有時候我們只想改變三維坐標中的一個維度,可是我們必須給transform.position一個三維坐標,毫無意外地此時的代碼會變得更長。為了解決這個問題,我們可以擴展出三個方法SetPositionX、SetPositionY、SetPositionZ來分別為x、y、z三個坐標分量進行賦值,我們繼續在SomeClass這個類中添加方法:

  1. ///<summary>
  2. ///設置TranformX坐標
  3. ///</summary>
  4. ///<param name="tran">當前Transform</param>
  5. ///<param name="x">X坐標</param>
  6. public static void SetPositionX(this Transform tran, float x)
  7. {
  8.     tran.position = new Vector3(x, tran.position.y, tran.position.z);
  9. }

10.

11.///<summary>

12.///設置TranformY坐標

13.///</summary>

14.///<param name="tran">當前Transform</param>

15.///<param name="x">Y坐標</param>

16.public static void SetPositionY(this Transform tran, float y)

17.{

  1. tran.position = new Vector3(tran.position.x, y, tran.position.z);

19.}

20.

21.///<summary>

22.///設置TranformZ坐標

23.///</summary>

24.///<param name="tran">當前Transform</param>

25.///<param name="x">Z坐標</param>

26.public static void SetPositionZ(this Transform tran, float z)

27.{

  1. tran.position = new Vector3(tran.position.x, tran.position.y, z);

29.}

使用擴展方法的利弊

  擴展方法使用起來得心應手,所以我們這里來討論下使用擴展方法的利弊。好處當然是自由而任性地使用擴展方法對類進行擴展,而且擴展方法在Visual Studio中的智能提示會以藍色向下箭頭進行標識。擴展方法的壞處則是要看設計擴展方法的人能否較好的駕馭這個特性啦,其實所有的技術都是一樣的,我常常在游戲群里聽到人鄙視Unity3D引擎,以UnReal Engine4為游戲引擎世界里的泰山北斗,我承認UE4的畫面效果好,可是能真正用好這個引擎的人有多少呢?擴展方法在使用的時候應該遵守就近原則,即是在最小的范圍內使用擴展方法,對具體類而非抽象類實現擴展方法。我們使用擴展方法無非是因為它在邏輯層需要這樣的功能,所以我們沒有必要去改變抽象層的邏輯,因為這樣會“污染”整個代碼。舉一個簡單的例子,我們知道.NET中的基類是object,如果我們對這個類進行擴展,毫無疑問它會影響所有繼承自object的類,這樣就會造成“污染”,顯然是不可取的。

小結

    • 在C#中實現擴展方法的類必須是靜態類且類的名稱和實現擴展方法的類無關
    • 實現擴展方法的類方法必須是靜態方法
    • 實現擴展方法的類方法的第一個參數必須是使用this關鍵字指明要實現擴展方法的類
    • 實現擴展方法應遵守就近原則,在最小的范圍內使用擴展方法以避免造成“污染”

 

GPS平台、網站建設、軟件開發、系統運維,找森大網絡科技!
http://cnsendnet.taobao.com
來自森大科技官方博客
http://www.cnsendblog.com/index.php/?p=476


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM