通過前面兩節的學習,我們了解到:擴展方法是一種特殊的靜態方法,擴展方法的定義方法和一般的靜態方法的定義方法類似,唯一的區別是在第一個參數的前面要加上關鍵字this作為修飾符。
擴展方法的調用方式和擴展類型上的實例方法的調用方式一樣。既然擴展方法可以像擴展類型上的實例方法一樣進行調用,那麼編譯器怎樣決定是否要使用一個擴展方法呢。對於這個問題,編譯器是按照下面的流程來工作的:當編譯器發現一個表達式好像是使用一個實例方法,但是在導入的所有命名空間和當前的命名空間中又沒有找到與這個實例方法調用兼容的實例方法,就會在導入的所有命名空間和當前的命名空間中查找一個合適的擴展方法。在這裡,我們記住兩點就可以了:(1)實例方法先於擴展方法被使用;(2)查找擴展方法的范圍是導入的所有命名空間和當前的命名空間,這個很好理解。
其實,還有一個問題需要闡述一下,當查找結果中存在多個適用的擴展方法時,編譯器是怎樣取捨的呢。其實編譯器只需在重載的方法中尋找最合適的一個即可。
下面來看個簡單的例子,希望通過這個例子能讓大家對擴展函數的調用有更深的了解。
第一步,在命名空間ConsoleApplication2下創建擴展方法Print(),使用該擴展方法可以完成簡單的打印操作,該擴展方法位於靜態類ExtendFunction中。
[csharp]
namespace ConsoleApplication2
{
public static class ExtendFunction
{
public static void Print(this object o)
{
Console.WriteLine(o);
}
}
}
namespace ConsoleApplication2
{
public static class ExtendFunction
{
public static void Print(this object o)
{
Console.WriteLine(o);
}
}
}
第二步,在命名空間ConsoleApplication2下創建類TestClass,利用該類中的Print()方法同樣可以完成打印操作。
[csharp]
namespace ConsoleApplication2
{
public class TestClass
{
private object priStr;
public TestClass(object o)
{
this.priStr = o;
}
public void Print()
{
Console.WriteLine(this.priStr);
}
}
}
namespace ConsoleApplication2
{
public class TestClass
{
private object priStr;
public TestClass(object o)
{
this.priStr = o;
}
public void Print()
{
Console.WriteLine(this.priStr);
}
}
}
第三步,在Main函數實現以下函數調用的代碼:
[csharp]
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
string s = "hell,world!";
int i = 32;
TestClass tc1 = new TestClass(s);
TestClass tc2 = new TestClass(i);
//實例方法調用
tc1.Print();
//靜態方法調用
s.Print();
//實例方法調用
tc2.Print();
//靜態方法調用
i.Print();
}
}
}
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
string s = "hell,world!";
int i = 32;
TestClass tc1 = new TestClass(s);
TestClass tc2 = new TestClass(i);
//實例方法調用
tc1.Print();
//靜態方法調用
s.Print();
//實例方法調用
tc2.Print();
//靜態方法調用
i.Print();
}
}
}
第四步,執行程序,得到以下結果:
其實對於這個結果,我們應該早就預料到了。因為該實例中的擴展方法Print()與TestClass類提供的Print()方法從功能上來說是完全等價的。
通過這個例子,讓我們進一步了解到,擴展方法的調用方式和擴展類型上的實例方法的調用方式完全一樣,如本例中的“tc1.Print()”及“s.Print()”。
更重要的是,通過在方法前設置斷點加單步調試來跟蹤代碼流走方向,讓我們進一步認識到:實例方法確實是先於擴展方法被使用的。那我又是怎樣確定這點的呢?現在我就將我的做法娓娓道來:(1)首先,在擴展方法Print()及TestClass類的成員方法Print()前設置斷點;(2)接下來,啟動單步調試,跟蹤代碼流走方向,注意到:函數調用“tc1.Print()”及“tc2.Print()”調用的方法是TestClass類的實例方法Print();而函數調用“s.Print()”及“i.Print()”調用的是擴展方法Print();(3)緊接著,將TestClass類的成員方法Print()注釋,再次啟動單步調試跟蹤代碼流走方向,發現:此次函數調用“tc1.Print()”,“tc2.Print()”,“s.Print()”,“i.Print()”均調用的是擴展方法Print(),完全是按照“先實例方法再擴展方法”的調用流程來完成函數調用。當然,此次程序的運行結果與前次也有一定的差別,具體結果見下圖:
為什麼會這樣呢?原因很簡單,你說是嗎?好好想想吧。
最後,在靜態類ExtendFunction中添加以下代碼:
[csharp]
public static void Print(this object o, bool isPrint)
{
if (isPrint == true)
{
Console.WriteLine(o);
}
}
public static void Print(this object o, bool isPrint)
{
if (isPrint == true)
{
Console.WriteLine(o);
}
}
再次啟動單步調試,發現函數調用“s.Print()”及“i.Print()”調用的是擴展方法是具有“public static void Print(this object o)”這個函數前面的擴展方法,而不是具有“public static void Print(this object o, bool isPrint)”這個函數前面的擴展方法。看來存在多個適用的(僅方法名稱相同)擴展方法時,編譯器確實是在重載的方法中尋找最合適的一個來執行的。