C# 3.X、4.X新特性:Extension Method
上周我介绍了C#的一个新特性Automatically Impemented Propert,本次我将介绍另外一个新特性Extension Method。
在学习这个新特性之前我们先了解一下Javascript的Prototype属性,我们都知道Javascript是一种动态语言,可以利用 Prototype属性在不改变原有对象的基础上对对象进行扩展,这种特性是Javascript开发变得非常灵活快捷,下面我们看个具体的例子。
function Boy(name) { this.Name= name }
这样我们就定义好了一个Javascript对象,我们可以向这样来对它进行实例化
var boy = new Boy("Young");
现在我们对Boy对象进行扩展,增加一个Speek方法,并调用
function Boy(name) { this.Name = name } Boy.prototype.Speek = function () { alert("MyName is " + this.Name); } var boy = new Boy("Young"); boy.Speek()
通过上面的例子大家可以看出Prototype属性是实际应用中的作用,C#的Extension Method特性就如同Prototype的作用一样。
在C#2.0时,我们想对一个已经存在的对象或接口进行扩展非常麻烦。如果是接口,则要同步修改所有具体实现的类。而如果第三方仅仅提供了已编译的程序集,则没办法做任何修改。通常我们会运用适配器模式重新定义一个新的类以便重用已有的类。
下面我们看看C#3.0是如何解决这个扩展问题的。简单的说Extension Method是一个定义在Static Class的一个特殊的Static Method。之所以说它是一个特殊的静态方法,是因为它不但可以像普通静态方法一样调用,也可以通过实例化后的对象调用。
public class Boy { public string Name { get; set; } public int Age { get; set; } publicBoy(string n, inta) { this.Name= n; this.Age= a; } } public static class Extension { public static StringIntroduce(this Boyboy) { returnstring.Format("Myname is {0},I am {1} years old",boy.Name,boy.Age.ToString()); } }
现在我们可以在实例化的boy对象中调用Introduce方法了。
var boy = new Boy("Young",100); Response.Write(boy.Introduce());
我们现在再看看那个扩展方法Introduce,和一般的静态方法不同的是,在第一个参数前添加了一个this关键字,这是3.0中定义Extension Method而引入的关键字,添加了这样一个关键字就意味着在调用该方法的时候这个标记有this的参数可以前置,从而允许我们向调用一般Instance Method的方式来调用这个Static Method。
还和上一篇一样,我们看看编译器是如何处理这个扩展方法的。
为何比较这个扩展方法和普通方法有什么不同,我们编写了一个普通的静态方法Discribe。
public static class Extension { public static StringIntroduce(this Boyboy) { returnstring.Format("Myname is {0},I am {1} years old",boy.Name,boy.Age.ToString()); } public static StringDiscribe(Boy boy) { returnstring.Format("Myname is {0},I am {1} years old", boy.Name, boy.Age.ToString()); } }
然后我们再用reflector看看编译生成的IL
通过对比我们发现,Introduce和普通静态方法Discribe生成的IL唯一的区别就是在Introduce方法最开始添加了下面一段代码:
.custom instance void[System.Core]System.Runtime.CompilerServices.ExtensionAttribute::.ctor()
这段代码就是在Introduce方法上添加了一个自定义属性,System.Runtime.CompilerServices.ExtensionAttribute。所以Introduce的定义方式等价于如下代码
[ExtensionAttribute] public static StringDiscribe(Boy boy) { return string.Format("My name is {0},I am {1} years old",boy.Name, boy.Age.ToString()); }
但是,System.Runtime.CompilerServices.ExtensionAttribute和其他Custom Attribute不一样,因为它是为了Extension Method的而定义的,我们只能通过添加this Key word的语法来定义Extension Method。所以当我们将System.Runtime.CompilerServices.ExtensionAttribute直接运用到Introduce方法会出现下面的Compile Error:
Donot use 'System.Runtime.CompilerServices.ExtensionAttribute'. Use the 'this'keyword instead.
通过对IL的分析,我们再来简单描述一下扩展方法的编译过程:当Compiler对Introduce方法的调用进行编译的过程的时候,它必须判断这个Introduce方式是Boy类型的成员还是以Extension Method的方式定义。Extension Method的优先级是最低的,只有确定Boy中没有定义相应的Introduce方法的时候,Compiler才会在引用的Namespace中查看这些Namespace中是否定义有对应的Introduce Extension Method的Static Class。找到后作进行相应的编译,否则出现编译错误。