原文:《C# Version 3.0 Specification》,Microsoft
翻譯:lover_P
擴展方法(Extension Methods)是一種靜態方法,可以通過實例方法的語法進行調用。從最終效果上看,擴展方法使得擴展一個現有類型和構造一個具有附加方法的類型變成了現實。
注意
擴展方法很難發覺,並且比起實例方法在功能性上有很大限制。出於這些原因,我們建議保守地使用擴展方法,僅在實例方法不大可行或根本不可行的時候才使用。
擴展成員的其他類型,如屬性、事件和運算符都在考慮之中,但目前並未支持。
2.1 聲明擴展方法
擴展方法通過在方法的第一個參數上指定關鍵字this作為一個修飾符來聲明。擴展方法只能聲明在靜態類中。下面的示例是一個聲明了兩個擴展方法的靜態類:
namespace Acme.UtilitIEs
{
public static class Extensions
{
public static int ToInt32(this string s) {
return Int32.Parse(s);
}
public static T[] Slice<T>(this T[] source, int index, int count) {
if(index < 0 || count < 0 || source.Length - index < count)
throw new ArugmentException();
T[] result = new T[count];
Array.Copy(source, index, result, 0, count);
return result;
}
}
}
擴展方法和正常的靜態方法具有完全相同的功能。另外,一旦導入了擴展方法,就可以用調用實例方法的語法來調用擴展方法。
2.2 導入擴展方法
擴展方法使用using-namespace-directives導入。除了導入一個命名空間中的類型以外,一個using-namespace-directive還可以導入一個命名空間中所有的靜態類中所有的擴展方法。最後,導入的擴展方法表現為其第一個參數的類型的附加方法,並且其優先級比一般的實例方法低。例如,當使用using-namespace-directive導入了上面例子中的Acme.UtilitIEs命名空間時:
using Acme.UtilitIEs;
就可以使用調用實例方法的語法來調用靜態類Extensions中的擴展方法了:
string s = "1234";
int i = s.ToInt32(); // 和Extensions.ToInt32(s)一樣
int[] digits = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int[] a = digits.Slice(4, 3); // 和Extensions.Slice(digits, 4, 3)一樣
2.3 擴展方法的調用
下面描述了擴展方法調用的詳細規則。在下面這些形式的方法調用中:
expr . identifIEr ( )
expr . identifIEr ( args )
expr . identifIEr < typeargs > ( )
expr . identifIEr < typeargs > ( args )
如果按照正常的過程沒有發現可用的實例方法(確切地說,當待調用的候選方法集合為空時),就會嘗試構造一個擴展方法調用。這些方法調用首先被重寫為下面相應的形式:
identifIEr ( expr )
identifIEr ( expr , args )
identifIEr < typeargs > ( expr )
identifIEr < typeargs > ( expr , args )
然後將重寫後的形式作為一個靜態方法調用進行處理,identifier按照下列順序進行解析:首先是命名空間生命中最接近的聲明,然後是每一個接近的命名空間,最後是包含這些代碼的編譯單元,其間不斷嘗試重寫過的方法調用,這些方法來自一個方法組,該組由using-namespace-directives導入的命名空間中所有可見的identifIEr所提供的可見的擴展方法構成。第一個產生了非空候選方法集合的方法組是對沖洗過的方法調用的一個選擇。如果所有的嘗試都產生了空的候選方法集合,就會出現一個編譯期錯誤。
上述規則意味著實例方法的優先級勝於擴展方法,並且最後引入的命名空間中的擴展方法的優先級勝於較先引入的命名空間中的擴展方法。例如:
using N1;
namespace N1
{
public static class E
{
public static void F(this object obj, int i) { }
public static void F(this object obj, string s) { }
}
}
class A { }
class B
{
public void F(int i) { }
}
class C
{
public void F(object obj) { }
}
class X
{
static void Test(A a, B b, C c) {
a.F(1); // E.F(object, int)
a.F("Hello"); // E.F(object, string)
b.F(1); // B.F(int)
b.F("Hello"); // E.F(object, string)
c.F(1); // C.F(object)
c.F("Hello"); // C.F(object)
}
}
在這個例子中,B的方法優先於第一個擴展方法,而C的方法優先於所有兩個擴展方法。