c#3.0系列:Extension Method
我们说在C#3.0中,引入了一些列新的特性,但是个人认为Extension Method这个特性是最爽的,最有创新的。
它真正的解决了:在保持现有Type原封不动的情况下对其进行扩展,你可以在对Type的定义不做任何变动的情况下,为之添加所需的方法成员。下面我就来讲讲。
C#3.X出来之前
大家都知道javascript有个特新Prototype,它就如同C#3.X中的Extension Method。这里不多将了。
我们主要看看.NET的实现。在C#3.X出来之前我们可以做到对Type进行扩展。
interface的情况:
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的语法进行调用。
我们还是先来看例子:
{
// 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有什么区别。
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的。
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。找到后作进行相应的编译,否则出现编译错误。
扩展接口类型
看下面的例子
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 的智能提示将扩展函数标记为向下的蓝色箭头。