香蕉视频app

香蕉视频appKeep on going never give up.

Let's Go

C# 學習筆記(59)擴展方法

C#Lonely2019-10-04 14:51:4399次2條

什么是擴展方法?

香蕉视频app擴展方法,首先是一種方法,它可以用來擴展已定義類型中的方法成員。


擴展方法的定義規則

香蕉视频app1、擴展方法必須在一個非嵌套、非泛型的靜態類中定義;

香蕉视频app2、擴展方法至少要有一個參數;

香蕉视频app3、擴展方法第一個參數必須加上this關鍵字作為前綴(第一個參數類型也稱為擴展類型,即方法對這個類型進行擴展);

香蕉视频app4、擴展方法第一個參數不能使用任何其他的修飾符(如不能使用ref、out等修飾符);

香蕉视频app5、擴展方法第一個參數的類型不能是指針類型;

這些規則都是硬性規定,無論違反了哪一條,編譯器都可能報錯,或認為它不是一個擴展方法。


香蕉视频app下面簡單演示一下擴展方法的定義,代碼示例如下:

//擴展方法必須在非泛型靜態類中定義
public static class Extensions
{
    /// <summary>
    /// 獲取格式化字符串,帶時分秒,格式:"yyyy-MM-dd HH:mm:ss"
    /// </summary>
    /// <param name="dateTime">日期</param>
    /// <param name="isRemoveSecond">是否移除秒</param>
    public static string ToDateTimeString(this DateTime dateTime, bool isRemoveSecond = false)
    {
        if (isRemoveSecond)
            return dateTime.ToString("yyyy-MM-dd HH:mm");
        return dateTime.ToString("yyyy-MM-dd HH:mm:ss");
    }

    /// <summary>
    /// 獲取格式化字符串,不帶時分秒,格式:"yyyy-MM-dd"
    /// </summary>
    /// <param name="dateTime">日期</param>
    public static string ToDateString(this DateTime dateTime)
    {
        return dateTime.ToString("yyyy-MM-dd");
    }

    /// <summary>
    /// 獲取格式化字符串,不帶時分秒,格式:"yyyy-MM-dd"
    /// </summary>
    /// <param name="dateTime">日期</param>
    public static string ToDateString(this DateTime? dateTime)
    {
        if (dateTime == null)
            return string.Empty;
        return ToDateString(dateTime.Value);
    }
}

成功定義了擴展方法后,接下來就該去調用它了,具體演示代碼如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            DateTime dt_1 = DateTime.Now;
            DateTime? dt_2 = DateTime.Now;
            DateTime? dt_3 = null;

            //以下代碼中,調用了我們定義的擴展方法,從調用過程來看擴展方法的調用與普通方法沒什么不同。
            string strDt_1 = Extensions.ToDateTimeString(dt_1);
            string strDt_2 = Extensions.ToDateString(dt_2);          
            string strDt_3 = Extensions.ToDateString(dt_3);

            Console.WriteLine(strDt_1);
            Console.WriteLine(strDt_2);
            Console.WriteLine(strDt_3);

            //然而,擴展方法還有另外一種調用方式,代碼如下:
            string strDt_4 = dt_1.ToDateTimeString();
            string strDt_5 = dt_2.ToDateString();
            string strDt_6 = dt_3.ToDateString();

            Console.WriteLine(strDt_4);
            Console.WriteLine(strDt_5);
            Console.WriteLine(strDt_6);

            Console.ReadKey();
        }
    }
}

從以上代碼中,可以看出擴展方法調用的獨特性,即可以直接通過DateTime類型來調用擴展方法。大家可能會認為我的代碼寫錯了,因為在DateTime類型中根本沒有定義這些實例方法,然而事實確是,上面的調用方式完全正確,運行結果如下:

image.png


編譯器如何發現擴展方法

剛接觸擴展方法的朋友肯定會對以上代碼的調用有所疑惑。而對于C# 3.0編譯器而言,當它看到某個類型的變量在調用方法時,它會首先去該對象的實例方法中進行查找,如果沒有找到與調用方法同名并參數一致的實例方法,編譯器就會去查找是否存在合適的擴展方法。編譯器會檢查所有導入的命名空間和當前命名空間中的擴展方法,并將變量類型匹配到擴展類型。

香蕉视频app你可以通過前面介紹的擴展方法規則和編譯器的智能提示來識別擴展方法,在VS中,擴展方法前面都有一個向下的箭頭標識,如下所示:

image.png

但是這只是人們識別擴展方法的方式,編譯器則會根據System.Runtime.CompilerServices.ExtensionAttribute屬性來識別擴展方法。如果我們定義的方法是擴展方法的話,該屬性就會被自動綁定到方法上。我們可以通過反編譯工具來查看之前定義的擴展方法,如下所示:

image.png

從編譯器發現擴展方法的過程來看,方法的調用順序應為:類型的實例方法 -> 當前命名空間下的擴展方法 -> 導入命名空間的擴展方法。要想使用不同命名空間的擴展方法,要先引入該命名空間。


空引用也可以調用擴展方法

在C#中,在空引用(即null)上調用實例方法是會引發NullReferenceException異常的,但在空引用上卻可以調用擴展方法。你可能會說:“這怎么可能呢?”然而事實上確是如此。為了證明“代碼在空引用上調用擴展方法是可以正常運行的”這個結論,我們來看如下代碼:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {

            Console.WriteLine("空引用上調用擴展方法演示:");

            string str = null; //str為空引用

            //在空引用上調用擴展方法,不會發生NullReferenceException異常
            bool myBool = str.IsNull();

            Console.WriteLine("字符串str為空字符串:{0}",myBool);

            Console.ReadKey();
        }
    }

    //擴展方法必須在非泛型靜態類中定義
    public static class Extensions
    {
        //不規范的定義擴展方法的方式
        public static bool IsNull(this object obj)
        {
            return obj == null;
        }

    }

}

以上代碼在空引用上調用擴展方法時確實沒有出現NullReferenceException異常,運行結果如下所示:

image.png

在前面這段代碼中,擴展方法的定義方式是不規范的。代碼擴展了object類型,所有繼承于object的類型都將具有該擴展方法,這就對其他的子類型產生了“污染”。之所以叫它污染,是因為我們定義的擴展方法本來只是想擴展某個子類(如前面的代碼只想擴展string類型),卻意外的造成了原本不想造成的后果。更好的實現方式應該如下代碼所示:

public static bool IsNull(this string str)
{
    return str == null;
}

所以當我們為一個類型定義擴展方法時,應盡量擴展具體的類型,而不要擴展其基類。

在空引用上調用擴展方法之所以不會出現NullReferenceException異常,是因為對于編譯器而言,這個過程只是把空引用“str”當成參數傳入靜態方法而已,即str.IsNull的調用等效于下面的代碼:

bool myBool = Extensions.IsNull(str);

Console.WriteLine("字符串str為空字符串:{0}",myBool);

這并不是真正地在空引用上調用方法,所以也就不存在出現NullReferenceException異常的問題了。

為了證實前面的結論,我們來看一下前面例子所對應的IL代碼:

image.png

以上紅框就是str.IsNull相對應的IL代碼,由此可以看出此時確是是把空引用str當作參數傳入了靜態類Extensions,從而調用了靜態方法IsNull,這也證實了前面的結論。

暗錨,解決錨點偏移

文章評論

    嘿,來試試登錄吧!