我們說在C#3.0中,引入了一些列新的特性,但是個人認為Extension Method 這個特性是最爽的,最有創新的。
它真正的解決了:在保持現有Type原封不動的情況下對其進行擴展,你可以 在對Type的定義不做任何變動的情況下,為之添加所需的方法成員。下面我就來 講講。
C#3.X出來之前
大家都知道javascript有個特新Prototype,它就如同C#3.X中的Extension Method。這裡不多將了。
我們主要看看.NET的實現。在C#3.X出來之前我們可以做到對Type進行擴展。
interface的情況:
1public interface IEmployee
2{
3 string Name { get; set; }
4 int Age{get; set;}
5 int add(int n);
6}
對這個Interface進行擴展,為之添加一個Add方法執行相關的運算。我們唯 一的解決方案就是直接在這個Interface中添加一個Add成員。如上。實現了這個 Interface的Type必須實現該Interface的所有方法。所以,我們添加了Add這個 方法,將導致所有實現它的Type的重新定義和編譯,在很多情況下,我們根本不 需要這樣。
Class的情況:
如果我們將一個class作為基類,在基類中添加一個Add Method,所有的 Child Class都不會受到影響。但是在很多情況下,對於我們需要擴展的 Interface或者是class,我們是完全不能做任何改動。比如,我們要對datagrid 控件進行擴展。我們常用的方法就自定義一個Class去繼承這個datagrid,將需 要添加的成員定義在我們自己定義的Class中,這就是我們常說的自定義控件, 如果對於一個Sealed Class又該如何呢?我們要求的是對這個不能變動的Type進 行擴展,也就是使這個不能變動的Type的Instance具有我們添加的對象。
如果聽到這樣的要求:我們要對一個Type或者Interface進行擴展,卻不允許 我們修改它。這個要求確實有點苛刻。但是c#3.x 中我們可以選擇Extension Method。Extension Method本質上是在被擴展的對象實例上可以調用的靜態函數 ,不是繼承,所以不同於普通的成員函數,擴展函數不能直接訪問被擴展對象的 成員。只能通過該對象的實例來訪問。
C#3.X出來之後
簡單地說Extension Method是一個定義在Static Class的一個特殊的Static Method。之所以說這個Static Method特別,是因為Extension Method不但能 按照Static Method的語法進行調用,還能按照Instance Method的語法進行調用 。
我們還是先來看例子:
public static class MyExtensionMethods
{
// this代表擴展方法應用於string類型上
public static int ToInt32(this string s)
{
int i;
Int32.TryParse(s, out i);
return i;
}
}
public static void fnExtensionMethod()
{
string s = "27";
// 使用string的ToInt32()擴展方法
int i = s.ToInt32();
}
我們可以看看上面的例子,我們知道net framework 裡string是個Sealed 類 型,我們只能使用Extension Method來對其進行擴展。我們可以看看它的定義方 式。ToInt32是一個Static方法。和一般的Static方法不同的是:在第一個參數 前添加了一個this 關鍵字。這是在C# 3.0中定義Extension Method而引入的關 鍵字。添加了這樣一個關鍵字就意味著在調用該方法的時候這個標記有this的參 數可以前置,從而允許我們向調用一般Instance Method的方式來調用這個 Static Method。注意:需要在(只需要在)第一個參數前面使用this修飾。
Extension Method IN CLR
C# 3.0的這些新的特性大都影響Source被C# Compiler編譯成Assembly這個階 段,換句話說,這些新特僅僅是Compiler的新特性而已。通過對Compiler進行修 正,促使他將C# 3.0引入的新的語法編譯成相對應的IL Code,從本質上看,這 些IL Code 和原來的IL並沒有本質的區別。所有當被編譯生成成Assembly被CLR 加載、執行的時候,CLR是意識不到這些新的特性的。
我們先看看Extension Method與傳統的Static Method有什麼區別。
1.method public hidebysig static int32 ToInt32(class ConsoleApplication1.person s) cil managed
2{
3 .custom instance void [System.Core] System.Runtime.CompilerServices.ExtensionAttribute::.ctor()
4 .maxstack 2
5 .locals init (
6 [0] int32 i,
7 [1] int32 CS$1$0000,
8 [2] int32 CS$0$0001)
9 L_0000: nop
10 L_0001: ldarg.0
11 L_0002: callvirt instance int32 ConsoleApplication1.person::get_Age()
12 L_0007: stloc.2
13 L_0008: ldloca.s CS$0$0001
14 L_000a: call instance string [mscorlib]System.Int32::ToString ()
15 L_000f: ldloca.s i
16 L_0011: call bool [mscorlib]System.Int32::TryParse(string, int32&)
17 L_0016: pop
18 L_0017: ldloc.0
19 L_0018: stloc.1
20 L_0019: br.s L_001b
21 L_001b: ldloc.1
22 L_001c: ret
23}
24
25
26
27
28
大家注意,這是Extension Method的IL,它與傳統的Static Method最大的區 別事在Extension Method多了這句:
.custom instance void [System.Core] System.Runtime.CompilerServices.ExtensionAttribute::.ctor()
就是在ToInt32方法上添加一個Customer Attribute: System.Runtime.CompilerServices.ExtensionAttribute。這個
屬性是為了Extension Method的而定義的。
我們再來看看,IL事如何實現Instance Method方式調用Extension Method的 。
1.method public hidebysig static void fnExtensionMethod() cil managed
2{
3 .maxstack 1
4 .locals init (
5 [0] string s,
6 [1] int32 i,
7 [2] int32 j)
8 L_0000: nop
9 L_0001: ldstr "27"
10 L_0006: stloc.0
11 L_0007: ldloc.0
12 L_0008: call int32 ConsoleApplication1.MyExtensionMethods::ToInt32(string)
13 L_000d: stloc.1
14 L_000e: ldstr "28"
15 L_0013: call int32 ConsoleApplication1.MyExtensionMethods::ToInt32(string)
16 L_0018: stloc.2
17 L_0019: ldloca.s i
18 L_001b: call instance string [mscorlib]System.Int32::ToString ()
19 L_0020: call void [mscorlib]System.Console::WriteLine(string)
20 L_0025: nop
21 L_0026: ret
22}
23
24
通過上面的IL,我們看到調用的是 ConsoleApplication1.MyExtensionMethods的ToInt32方法。
我們基本上看出了Extension Method的本質。當Compiler對Adds方法的調用 進行編譯的過程的時候,它必須判斷這個ToInt32方式是String已有的成員還是 以Extension Method的方式定義。Extension Method的優先級是最低的,只有確 定String中沒有定義相應的ToInt32方法的時候,Compiler才會在引用的 Namespace中查看這些Namespace中是否定義有對應的ToInt32 Extension Method 的Static Class。找到後作進行相應的編譯,否則出現編譯錯誤。
擴展接口類型
看下面的例子
1public static void fnIntExtensionMethod()
2 {
3
4 Console.WriteLine("***** Extending an interface *****\n");
5 // Call IMath members from MyCalc object.
6 MyCalc c = new MyCalc();
7 Console.WriteLine("1 + 2 = {0}", c.Add(1, 2));
8 Console.WriteLine("1 - 2 = {0}", c.Subtract(1, 2));
9 // Can also cast into IBasicMath to invoke extension.
10 Console.WriteLine("30 - 9 = {0}", ((IMath)c).Subtract (30, 9));
11 // This would NOT work!
12 //IMath itfBM = new IMath();
13 //itfBM.Subtract(10, 10);
14 Console.ReadLine();
15
16 }
17
18public static class MyExtensionMethods
19 {
20 // this代表擴展方法應用於string類型上
21 // ToInt32()是將string類型轉換為int類型的擴展方法
22 public static int ToInt32(this string s)
23 {
24 int i;
25 Int32.TryParse(s, out i);
26 return i;
27 }
28
29 public static int Subtract(this IMath itf ,int x, int y)
30 {
31 return x - y;
32 }
33
34 }
35public interface IMath
36 {
37 int Add(int x, int y);
38 }
39
40class MyCalc : IMath
41 {
42 public int Add(int x, int y)
43 {
44 return x + y;
45 }
46 }
注意這裡擴展時必須給出函數的實現,擴展接口後,顯然不能直接在接口上調 用這些擴展函數,只能理解為,所有繼承該接口的對象新增加了這些擴展函數功 能。
注意事項:
引用擴展函數
必須引用定義擴展函數的命名空間,否則擴展函數不可用。
智能提示
Visual studio 的智能提示將擴展函數標記為向下的藍色箭頭。