c# 擴展方法詳解

擴展方法被定義爲靜態方法,但它們是通過實例方法語法進行調用的。 它們的第一個參數指定該方法作用於哪個類型,並且該參數以 this 修飾符爲前綴。 擴展方法當然不能破壞面向對象封裝的概念,所以只能是訪問所擴展類的public成員。


        擴展方法使您能夠向現有類型“添加”方法,而無需創建新的派生類型、重新編譯或以其他方式修改原始類型。擴展方法是一種特殊的靜態方法,但可以像擴展類型上的實例方法一樣進行調用。


        C#擴展方法第一個參數指定該方法作用於哪個類型,並且該參數以 this 修飾符爲前綴。
        擴展方法的目的就是爲一個現有類型添加一個方法,現有類型既可以是int,string等數據類型,也可以是自定義的數據類型。例如爲數據類型的添加一個方法的理解:一般來說,int數據類型有個Tostring的方法,就是把int 數據轉換爲字符串的類型,比如現在我們想在轉換成字符串的時候還添加一點東西,比如增加一個字符 a .那麼之前的Tostring就不好使了,因爲它只是它我們的int數據轉換爲string類型的,卻並不能添加一個字母 a.所以這就要用到所謂的擴展方法了。首先我們看一個給現有的類型增加一個擴展方法。我們想給string 類型增加一個Add方法,該方法的作用是給字符串增加一個字母a.

 

實例1:

  //必須是靜態類纔可以添加擴展方法
    static class Program
    {
        static void Main(string[] args)
        {
            string str = "quzijing";
            //注意調用擴展方法,必須用對象來調用 
            string Newstr = str.Add();
            Console.WriteLine(Newstr);
            Console.ReadKey();
        }
        //聲明擴展方法
        //擴展方法必須是靜態的,Add有三個參數
        //this 必須有,string表示我要擴展的類型,stringName表示對象名
        //三個參數this和擴展的類型必不可少,對象名可以自己隨意取如果需要傳遞參數,//再增加一個變量即可
 
        public static string Add(this string stringName)
        {
            return stringName + "a";
        }
    }
 

我們再來嘗試給我們自定義的類型增加一個擴展方法,並增加一個傳遞的參數。

實例2:

首先我們聲明一個Student類,它包含了兩個方法StuInfo,getStuInfo.實例代碼如下:

    public class Student
    {
        public string StuInfo()
        {
            return "學生基本信息";
        }
        public  string getStuInfo(string stuName, string stuNum)
        {
       return string.Format("學生信息:\n" + "姓名:{0} \n" + "學號:{1}", stuName, stuNum);
        }
     }
 

之後我們再聲明一個名爲ExtensionStudentInfo的靜態類,注意必須爲靜態。這個類的作用就是包含一些我們想要擴展的方法,在此我們聲明兩個Student類型的擴展方法,Student類型爲我們自定義的類型。示例代碼如下:

    public static class ExtensionStudentInfo
    {
        //聲明擴展方法
        //要擴展的方法必須是靜態的方法,Add有三個參數
        //this 必須有,string表示我要擴展的類型,stringName表示對象名
        //三個參數this和擴展的類型必不可少,對象名可以自己隨意取如果需要傳遞參數,再增加一個變量即可
        public static string ExtensionStuInfo(this Student stuName)
        {
            return stuName.StuInfo();
        }
        //聲明擴展方法
        //要擴展的方法必須是靜態的方法,Add有三個參數
        //this 必須有,string表示我要擴展的類型,stringName表示對象名
        //三個參數this和擴展的類型必不可少,對象名可以自己隨意取如果需要傳遞參數,在此我們增加了兩個string類型的參數
        public static string ExtensionGetStuInfo(this Student student, string stuname, string stunum)
        {
            return student.getStuInfo(stuname, stunum)+"\n讀取完畢";
        }
    }

以上的工作做完之後便可以使用我們的擴展方法了,注意必須要用對象來調用擴展方法。

 

        static void Main(string[] args)
        {
            Student newstudent = new Student();
//要使用對象調用我們的擴展方法
            string stuinfo = newstudent.ExtensionStuInfo();
            Console.WriteLine(stuinfo);
//要使用對象調用我們的擴展方法
string stuinformation = newstudent.ExtensionGetStuInfo("quzijing", "20081766");
            Console.WriteLine(stuinformation);
            Console.ReadKey();
        }
 

實例3、使用TagBuilder類創建擴展方法

 上面自定義的Span()方法十分簡單, 但是有時候我們要構造具有複雜結構的Html元素, 如果用字符串拼接的方法就有些笨拙.ASP.NET MVC框架提供了一個幫助我們構造Html元素的類:TagBuilder

TagBuilder類有如下方法幫助我們構建Html控件字符串:

方法名稱    用途
AddCssClass()    添加class=””屬性
GenerateId()    添加Id,  會將Id名稱中的"."替換爲IdAttributeDotReplacement 屬性值的字符.默認替換成"_"
MergeAttribute()    添加一個屬性,有多種重載方法.
SetInnerText()    設置標籤內容, 如果標籤中沒有再嵌套標籤,則與設置InnerHTML 屬性獲得的效果相同.
ToString()    輸出Html標籤的字符串, 帶有一個參數的重載可以設置標籤的輸出形式爲以下枚舉值:
TagRenderMode.Normal -- 有開始和結束標籤
TagRenderMode.StartTag -- 只有開始標籤
TagRenderMode.EndTag -- 只有結尾標籤
TagRenderMode.SelfClosing -- 單標籤形式,如<br/>
同時一個TagBuilder還有下列關鍵屬性:

屬性名稱    用途
Attributes    Tag的所有屬性
IdAttributeDotReplacement    添加Id時替換"."的目標字符
InnerHTML    Tag的內部HTML內容
TagName    Html標籤名, TagBuilder只有帶一個參數-TagName的構造函數.所以TagName是必填屬性
下面在添加一個自定義的HtmlHelper類擴展方法,同樣是輸出一個<Span>標籤:

 

        public static string Span(this HtmlHelper helper, string id, string text, string css, object htmlAttributes)
        {
            //創意某一個Tag的TagBuilder
            var builder = new TagBuilder("span");
 
            //創建Id,注意要先設置IdAttributeDotReplacement屬性後再執行GenerateId方法.
            builder.IdAttributeDotReplacement = "-";
            builder.GenerateId(id);
            
 
            //添加屬性            
            builder.MergeAttributes(new RouteValueDictionary(htmlAttributes));
 
            //添加樣式
            builder.AddCssClass(css);
            //或者用下面這句的形式也可以: builder.MergeAttribute("class", css);
 
            //添加內容,以下兩種方式均可
            //builder.InnerHtml = text;
            builder.SetInnerText(text);
 
            //輸出控件
            return builder.ToString(TagRenderMode.Normal);
 
        }
在頁面上,調用這個方法:

<% =Html.Span("span.test", "使用TagBuilder幫助構建擴展方法", "ColorRed", new { style="font-size:15px;" })%>
生成的Html代碼爲:

<span id="span-test" class="ColorRed" style="font-size: 15px;">使用TagBuilder幫助構建擴展方法</span>
 

實例4

(1)、擴展方法

//聲明擴展方法的步驟:類必須是static,方法是static,
//第一個參數是被擴展的對象,前面標註this。
//使用擴展方法的時候必須保證擴展方法類已經在當前代碼中using
namespace 擴展方法
{
    //擴展方法必須是靜態的
    public static class StringHelper
    {
        //擴展方法必須是靜態的,第一個參數必須加上this
        public static bool IsEmail(this string _input)
        {
            return Regex.IsMatch(_input, @"^\\w+@\\w+\\.\\w+$");
        }
 
        //帶多個參數的擴展方法
        //在原始字符串前後加上指定的字符
        public static string Quot(this string _input, string _quot)
        {
            return _quot + _input + _quot;
        }
    }
}
(2)、使用方法

namespace 擴展方法
{
    class Program
    {
        static void Main(string[] args)
        {
            string _myEmail = "[email protected]";
            //這裏就可以直接使用string類的擴展方法IsEmail了
            Console.WriteLine(_myEmail.IsEmail());
            //調用接收參數的擴展方法
            Console.WriteLine(_myEmail.Quot("!"));
 
            Console.ReadLine();
        }
    }
}
 

實例5:(注意使用IL反編譯工具eg.  ILSpy)

string str = "abc";
object len = str.GetValueByName("Length");
string類型現在有了GetValueByName這個方法一樣,但實際上string並沒有這樣一個方法。那這又是爲什麼呢?是我們可愛的編譯器在其中做了手腳。爲了避開編譯器的干擾,我們來看MSIL反編譯後:

L_0008: ldstr "Length"
L_000d: call object TestLambda.PropertyExtension::GetValueByName(object, string)
從反編譯結果展示:

       代碼編譯後和調用靜態方法沒有任何的差別(從call指令來看,這是在調用一個靜態方法)。從這裏可以知道擴展方法即可以使用實例調用的方式也可以直接使用靜態類調用的方式。

str.GetValueByName("Length");
PropertyExtension.GetValueByName(str,"Length");
 

下面將對擴展方法做一些細節的介紹:

Visual Studio對擴展方法有智能感知的支持,如下圖:

在方法的圖標上有一個與其他的都不相同,他的突變下面還帶有一個藍色的向下的箭頭,這就表明這個方法是一個擴展方法。

 

 


總結:

在我們的編程生涯中我們要使用很多很多類庫,這些類庫有的是我們自己開發的,我們有她的代碼,有的是第三方發佈的,我們不僅沒有他們的代碼,連看的機會都沒有。

作爲.net程序員,我們每天都要和BCL(Base Class Linbrary)打交道。無疑,BCL做爲一個年輕的框架類庫,她是成功的,但是還有一些時候我們還是得寫一些”Helper”方法來擴展類庫,由於我們不能修改類庫的源代碼,我們只有寫一個個的靜態類。雖然在使用上也算方便,但作爲追求完美的程序員來說總有些不雅。 現在我就碰到這樣的事情,前兩天奉命寫一個從XML文件加載Chart圖的設置的方法,從XML加載數據綁定到對象上,這肯定是反射的用武之地了。我經常需要寫一些根據對象屬性名字來判斷這個對象是否有這個屬性或者根據屬性名獲取該屬性的值。還是按照平常一樣,我很快寫了一個PropertyHelper,裏面有兩個靜態方法:HasProperty,GetValueByName。

PropertyHelper.HasProperty(point, "X"),如此的調用也還過得去,不過在C# 3.0微軟爲我們提供了擴展方法。現在我們可以直接這樣調用了point.HasProperty(“X”);看看我是如何實現這個擴展方法的?

 

public static class PropertyExtension
{
        public static object GetValueByName(this object self, string propertyName)
        {
            if (self == null)
            {
                return self ;
            }
            Type t = self.GetType();
            PropertyInfo p = t.GetProperty(propertyName);
            return p.GetValue(self, null);
        }
}
 

我給object類型添加了一個擴展方法,在.net裏所有的類都繼承自object,那所有的類都默認的擁有這個方法了,真方便,呵呵。注意到和普通的靜態方法有何差別?在這個方法的第一個參數前面多了一個this關鍵字。

在擴展的時候也不要對比較高層的類進行擴展,像對object的擴展我覺得就是不可取的,object是所有類的基類,一經擴展,所有的類都被“污染”了。

 

擴展方法:

1 方法所在的類必須是靜態的

2 方法也必須是靜態的

3 方法的第一個參數必須是你要擴展的那個類型,比如你要給int擴展一個方法,那麼第一個參數就必須是int。

4 在第一個參數前面還需要有一個this關鍵字。

5、擴展方法有就近原則,也就是如果在你的程序裏有兩個一模一樣的擴展方法,一個和你的使用類是處於同一命名空間裏,另外一個處於別的命名空間裏,這個時候會優先使用同一命名空間裏的擴展方法,也就是說“血緣關係”越近,越被青睞。

6、注意的是擴展方法有“污染性”,所以我覺得在擴展的時候還是想想,是不是值得這樣擴展
————————————————
版權聲明:本文爲CSDN博主「zyh_1988」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/zyh_1988/article/details/51103612

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章