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。找到后作进行相应的编译,否则出现编译错误。

posted on 2010-06-22 00:41  龙骑士  阅读(579)  评论(0编辑  收藏  举报