规范
原文:《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的方法优先于所有两个扩展方法。