C#擴展方法知多少

當咱們想爲一個現有的類型添加一個方法的時候,有兩種方式:一是直接在現有類型中添加方法;可是不少狀況下現有類型都是不容許修改的,那麼可使用第二種方式,基於現有類型建立一個子類,而後在子類中添加想要的方法。html

當C# 2.0中出現了靜態類以後,對於上面的問題,咱們也能夠建立靜態工具類來實現想要添加的方法。這樣作能夠避免建立子類,可是在使用時代碼就沒有那麼直觀了。算法

其實,上面的方法都不是很好的解決辦法。在C# 3.0中出現了擴展方法,經過擴展方法咱們能夠直接在一個現有的類型上"添加"方法。當使用擴展方法的時候,能夠像調用實例方法同樣的方式來調用擴展方法。編程

擴展方法的使用

擴展方法的建立和使用仍是相對比較簡單的。ide

聲明擴展方法

相比普通方法,擴展方法有它本身的特徵,下面就來看看怎麼聲明一個擴展方法:工具

  • 它必須在一個非嵌套、非泛型的靜態類中(因此擴展方法必定是靜態方法)
  • 它至少要有一個參數
  • 第一個參數必須加上this關鍵字做爲前綴,且第一個參數類型也稱爲擴展類型(extended type),表示該方法對這個類型進行擴展
  • 第一個參數不能用其餘任何修飾符(好比out或ref)
  • 第一個參數的類型不能是指針類型
  • 擴展方法的類最好和調用擴展方法的類在同一個命名空間下。或使用using引用。(如:using TestWeb.Common;)

根據上面的要求,咱們給int類型添加了一個擴展方法,用來判斷一個int值是否是偶數:post

複製代碼
namespace ExtentionMethodTest
{
    public static class ExtentionMethods
    {
        public static bool IsEven(this int num)
        {
            return num % 2 == 0;
        }
    }

    class Program
    {
        static void Main(string[] args)
        { 
            int num = 10;
            //直接調用擴展方法
            Console.WriteLine("Is {0} a even number? {1}", num, num.IsEven());
            num = 11;
            //直接調用擴展方法
            Console.WriteLine("Is {0} a even number? {1}", num, num.IsEven());
            //經過靜態類調用靜態方法
            Console.WriteLine("Is {0} a even number? {1}", num, ExtentionMethods.IsEven(num));

            Console.Read();         
        }
    }
}
複製代碼

雖然這個例子很是簡單,但卻演示了擴展方法的使用。測試

調用擴展方法

經過上面的例子能夠看到,當調用擴展方法的時候,能夠像調用實例方法同樣。這就是咱們使用擴展方法的緣由之一,咱們能夠給一個已有類型"添加"一個方法。ui

既然擴展方法是一個靜態類的方法,咱們固然也能夠經過靜態類來調用這個方法。this

經過IL能夠看到,其實擴展方法也是編譯器爲咱們作了一些轉換,將擴展方法轉化成靜態類的靜態方法調用url

複製代碼
IL_001f: nop
IL_0020: ldc.i4.s 11
IL_0022: stloc.0
IL_0023: ldstr "Is {0} a even number? {1}"
IL_0028: ldloc.0
IL_0029: box [mscorlib]System.Int32
IL_002e: ldloc.0
//直接調用擴展方法
IL_002f: call bool ExtentionMethodTest.ExtentionMethods::IsEven(int32)
IL_0034: box [mscorlib]System.Boolean
IL_0039: call void [mscorlib]System.Console::WriteLine(string, object, object)
IL_003e: nop
IL_003f: ldstr "Is {0} a even number? {1}"
IL_0044: ldloc.0
IL_0045: box [mscorlib]System.Int32
IL_004a: ldloc.0
//經過靜態類調用靜態方法
IL_004b: call bool ExtentionMethodTest.ExtentionMethods::IsEven(int32)
IL_0050: box [mscorlib]System.Boolean
IL_0055: call void [mscorlib]System.Console::WriteLine(string, object, object)
IL_005a: nop
IL_005b: call int32 [mscorlib]System.Console::Read()
IL_0060: pop
IL_0061: ret
複製代碼

有了擴展方法,當調用擴展方法的時候,咱們就像是調用一個實例方法。可是,咱們應該從兩個角度看這個問題:

  • 經過擴展方法,可使一些方法的調用變得更加通俗易懂,與實例的關係看起來更協調。就例如,"num.IsEven()"這種寫法。
    • 基於這個緣由,能夠考慮把代碼中靜態工具類中的一些方法變成擴展方法
  • 固然正是因爲擴展方法的調用跟實例方法同樣,因此想要一眼就看出一個方法是否是擴展方法不那麼容易
    • 其實在VS中仍是很好辨別的,對於上面的例子,在VS中放上鼠標,就能夠看到"(extention) bool int.IsEven()"

擴展方法是怎樣被發現的

知道怎樣調用擴展方法是咱們前面部分介紹的,可是知道怎樣不調用擴展方法一樣重要。下面就看看編譯器怎樣決定要使用的擴展方法。

編譯器處理擴展方法的過程:當編譯器看到一個表達式好像是調用一個實例方法的時候,編譯器就會查找全部的實例方法,若是沒有找到一個兼容的實例方法,編譯器就會去查找一個合適的擴展方法;編譯器會檢查導入的全部命名空間和當前命名空間中的全部擴展方法,並匹配變量類型到擴展類型存在一個隱式轉換的擴展方法。

當編譯器查找擴展方法的時候,它會檢查System.Runtime.CompilerServices.ExtensionAttribute屬性來判斷一個方法是不是擴展方法

看到了編譯器怎麼處理擴展方法了,那麼就須要瞭解一下使用擴展方法時要注意的地方了。

擴展方法使用的注意點:

  • 實例方法的優先級高於擴展方法,當有擴展方法跟實例方法簽名一致的時候,編譯器不會給出任何警告,而是默認調用實例方法
  • 若是存在多個適用的擴展方法,它們能夠應用於不一樣的擴展類型(使用隱式轉換),那麼經過在重載的方法中應用的"更好的轉換"規則,編譯器會選擇最合適的一個
  • 在擴展方法的調用中,還有一個規則,編譯器會調用最近的namespace下的擴展方法
  • 擴展方法的類最好和調用擴展方法的類在同一個命名空間下。若是不在同一命名空間下,須要使用using將擴展方法的類的命名空間在當前調用的類裏引用一下,(如:using TestWeb.Common;)

下面看一個例子,經過這個例子來更好的理解編譯器處理擴展方法時的一些注意點:

複製代碼
namespace ExtentionMethodTest
{
    using AnotherNameSpace;
    public static class ExtentionMethods
    {
        public static void printInfo(this Student stu)
        {
            Console.WriteLine("printInfo(Student) from ExtentionMethodTest");
            Console.WriteLine("{0} is {1} years old", stu.Name, stu.Age);
        }

        public static void printInfo(this object stu)
        {
            Console.WriteLine("printInfo(object) from ExtentionMethodTest");
            Console.WriteLine("{0} is {1} years old", ((Student)stu).Name, ((Student)stu).Age);
        }
    }

    public class Student
    {
        public string Name { get; set; }
        public int Age { get; set; }

        //實例方法
        //public void printInfo()
        //{
        //    Console.WriteLine("{0} is {1} years old", this.Name, this.Age);
        //}
    }

    class Program
    {
        static void Main(string[] args)
        {
            Student wilber = new Student { Name = "Wilber", Age = 28 };
            //當實例方法printInfo存在的時候,全部的擴展方法都不可見
            //此時調用的是實例方法
            //wilber.printInfo();

            //當註釋掉實例方法後,下面代碼會調用最近的命名空間的printInfo方法
            //同時下面語句會選擇「更好的轉換」規則的擴展方法
            //printInfo(Student) from ExtentionMethodTest
            //Wilber is 28 years old
            wilber.printInfo();

            //當把wilber轉換成object類型後,會調用printInfo(this object stu)
            //printInfo(object) from ExtentionMethodTest
            //Wilber is 28 years old
            object will = wilber;
            will.printInfo();

            Console.Read();
        }
    }
}

namespace AnotherNameSpace
{
    using ExtentionMethodTest;
    public static class ExtentionClass
    {
        public static void printInfo(this Student stu)
        {
            Console.WriteLine("printInfo(Student) from AnotherNameSpace");
            Console.WriteLine("{0} is {1} years old", stu.Name, stu.Age);
        }
    }
}
複製代碼

空引用上調用擴展方法

當咱們在空引用上調用實例方法是會引起NullReferenceException異常的。

可是,咱們能夠在空引用上調用擴展方法。

看下面的例子,咱們能夠判斷一個對象是否是空引用。

複製代碼
namespace ExtentionMethodTest
{
    public static class NullUitl
    {
        public static bool IsNull(this object o)
        {
            return o == null;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            object x = null;
            Console.WriteLine(x.IsNull());
            x = new object();
            Console.WriteLine(x.IsNull());

            Console.Read();
        }
    }
}
複製代碼

經過上面的例子能夠看到,即便引用爲空,"x.IsNull()"仍然可以正常執行。

根據咱們前面介紹的擴展方法的工做原理,其實上面的調用會被編譯器轉換爲靜態方法的調用"NullUitl.IsNull(x)"(能夠查看IL代碼驗證),這也就解釋了爲何空引用上能夠調用擴展方法。

總結

本文介紹了擴展方法的使用以及工做原理,其實擴展方法的本質就是經過靜態類調用靜態方法,只不過是編譯器幫咱們完成了這個轉換。

而後還介紹了編譯器是如何發現擴展方法的,以及使用擴展方法時要注意的地方。瞭解了編譯器怎麼查找擴展方法,對編寫和調試擴展方法都是有幫助的。

 

出處:http://www.cnblogs.com/wilber2013/p/4307282.html

==============================================================================================

      前言:上篇 序列化效率比拼——誰是最後的贏家Newtonsoft.Json 介紹了下序列化方面的知識。看過Demo的朋友可能注意到了裏面就用到過泛型的擴展方法,本篇打算總結下C#擴展方法的用法。博主打算分三個層面來介紹這個知識點,分別是:.Net內置對象的擴展方法、通常對象的擴展方法、泛型對象的擴展方法。

     什麼是擴展方法?回答這個問題以前,先看看咱們通常狀況下方法的調用。相似這樣的通用方法你必定寫過:

複製代碼
        static void Main(string[] args)
        {

            string strRes = "2013-09-08 14:12:10";
            var dRes = GetDateTime(strRes);
        }

    
        //將字符串轉換爲日期
        public static DateTime GetDateTime(string strDate)
        {
            return Convert.ToDateTime(strDate);
        }

        //獲得非空的字符串
        public static string GetNotNullStr(string strRes)
        {
            if (strRes == null)
                return string.Empty;
            else
                return strRes;
        }
複製代碼

或者在項目中有一個相似Utils的工具類,裏面有多個Helper,例如StringHelper、XmlHelper等等,每一個Helper裏面有多個static的通用方法,而後調用的時候就是StringHelper.GetNotNullStr("aa");這樣。還有一種普通的用法就是new 一個對象,經過對象去調用類裏面的非static方法。反正博主剛開始作項目的時候就是這樣寫的。後來隨着工做經驗的累積,博主看到了擴展方法的寫法,立馬就感受本身原來的寫法太Low了。進入正題。

 

一、.Net內置對象的擴展方法

.Net內部也有不少定義的擴展方法,例如咱們Linq經常使用的Where(x=>x==true)、Select()等等。當你轉到定義的時候你很容易看出來:public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)。固然咱們也能夠給.Net對象新增擴展方法,好比咱們要給string對象加一個擴展方法(注意這個方法不能和調用的Main方法放在同一個類中):

複製代碼
        public static string GetNotNullStr(this string strRes)
        {
            if (strRes == null)
                return string.Empty;
            else
                return strRes ;
        }
複製代碼

而後在Main方法裏面調用:

        static void Main(string[] args)
        {
            string strTest = null;
            var strRes = strTest.GetNotNullStr();
        }

簡單介紹:public static string GetNotNullStr(this string strRes)其中this string就表示給string對象添加擴展方法。那麼在同一個命名空間下面定義的全部的string類型的變量均可以.GetNotNullStr()這樣直接調用。strTest.GetNotNullStr();爲何這樣調用不用傳參數,是由於strTest就是做爲參數傳入到方法裏面的。你能夠試試。使用起來就和.Net framework定義的方法同樣:

 

     固然除了string,你能夠給.Net內置的其餘對象加擴展方法,例如給DataGridViewRow的擴展方法:

//DataGridViewRow的擴展方法,將當前選中行轉換爲對應的對象
        public static T ToObject<T>(this DataGridViewRow item) where T:class
        {
            var model = item.DataBoundItem as T;
            if (model != null)
                return model;
            var dr = item.DataBoundItem as System.Data.DataRowView;
            model = (T)typeof(T).GetConstructor(new System.Type[] { }).Invoke(new object[] { });//反射獲得泛型類的實體
            PropertyInfo[] pro = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
            Type type = model.GetType();
            foreach (PropertyInfo propertyInfo in pro)
            {
                if (Convert.IsDBNull(dr[propertyInfo.Name]))
                {
                    continue;
                }
                if (!string.IsNullOrEmpty(Convert.ToString(dr[propertyInfo.Name])))
                {
                    var propertytype = propertyInfo.PropertyType;
                }
            }
            return model;
        }
View Code

這樣看上去就像在擴展.Net Framework。有沒有感受有點高大上~

 

二、通常對象的擴展方法

     和Framework內置對象同樣,自定義的對象也能夠增長擴展方法。直接上示例代碼:

    public class Person
    {
        public string Name { set; get; }
        public int Age { set; get; }
    }
複製代碼
        //Person的擴展方法,根據年齡判斷是不是成年人
        public static bool GetBIsChild(this Person oPerson)
        {
            if (oPerson.Age >= 18)
                return false;
            else
                return true;
        }
複製代碼

Main方法裏面調用:

var oPerson1 = new Person();
oPerson1.Age = 20;
var bIsChild = oPerson1.GetBIsChild();        

和string擴展方法相似,就很少作解釋了。

 

三、泛型對象的擴展方法

      除了上面兩種以外,博主發現其實能夠定義一個泛型的擴展方法。那麼,是否是全部的類型均可以直接使用這個擴展方法了呢?爲了保持程序的嚴謹,下面的方法可能沒有實際意義,當開發中博主以爲可能存在這種場景:

複製代碼
public static class DataContractExtensions
{
  //測試方法
  public static T Test<T>(this T instance) where T : Test2
  {
       T Res = default(T);
       try
       {
           Res.AttrTest = instance.AttrTest.Substring(0,2);
           //其餘複雜邏輯...


      }
      catch
      { }
      return Res;
  }

}

public class Test2
{
  public string AttrTest { set; get; }
}
複製代碼

 

使用擴展方法有幾個值得注意的地方:

(1)擴展方法不能和調用的方法放到同一個類中

(2)第一個參數必需要,而且必須是this,這是擴展方法的標識。若是方法裏面還要傳入其餘參數,能夠在後面追加參數

(3)擴展方法所在的類必須是靜態類

(4)擴展方法的類最好和調用擴展方法的類在同一個命名空間下。若是不在同一命名空間下,須要使用using將擴展方法的類的命名空間在當前調用的類裏引用一下,(如:using TestWeb.Common;)

 

可能你第一次使用這個會以爲很彆扭。你也許會說擴展方法和我之前用的static方法不管從代碼實現仍是算法效率都差很少嘛,是的!確實差很少,但使用多了以後會發現它確實能幫你省去不少代碼。

 

出處:http://www.cnblogs.com/landeanfen/p/4632467.html

===========================================================================================

另外參考微軟的說明文檔:

擴展方法(C# 編程指南)

如何:實現和調用自定義擴展方法(C# 編程指南)

如何:爲枚舉建立新方法(C# 編程指南)

相關文章
相關標籤/搜索