C# 3.0 feature 2--Extension methods
Extension method,可以对现有类功能进行扩充,从而使该类型的实例具有更多的方法(功能)。
有人可能会提出疑问,如果是对类进行扩充,那使用继承不就ok了?是的,使用继承确实可以达到扩充功能的目的,但也会带来更多的问题,比如该使用子类的地方,传入的却是父类对象;比如想扩充的类为第三方提供,我们无法得知其内部细节等等。抛开这些统统不谈,为了让一个类型的实例增加一个方法,我们就要使用继承创造自己的子类?这显然不是一个好办法,相对于这些, Extension method给我们提供了一个简便,灵活的解决方案。
我们先来看一个例子。
上面的例子中,我们对int的方法进行了扩充,为其增加了Square方法。
下面是编绎后的IL代码。
我们发现原来extension method也没什么玄乎的,编绎器会将其编绎为对Square(i)的调用,原来就是静态类中方法调用。
将Extension Method改为静态方法,并在Main函数中直接调用
可以看到,两种代码产生的IL代码一模一样。
下面说下如何定义Extension Method,首先,Extension Method必须定义在普通的 static class内,这里讲的普通是static class不能为泛型(non-generic)或者内嵌(non-nested)的。其次,Extension Method的第一个参数必须为this + 类型 + 变量名。该参数指定了拥有该方法的类型为 this指定的类型。可能有的人会问,即然this指定的类型拥有,那么可不可以使用类型的私有或受保护的成员?很遗憾,不能。记住,Extension Method仅仅是看起来像是一个类型的方法,但其实质上不是,它更像是静态类型的静态方法,事实上,它确实拥有静态方法所具有的所有功能。
说完了这些,再说一下Extension Method的作用域,它是整个namespace可见的,并且可以通过using namespace来导入其它命名空间中的Extension Method. 导入的扩导方法有没有可能因为跟原类型的重名方法冲突而导致编绎失败呢?不会。如果有同名的方法,Extension Method会出现在该方法Intellisense中的Parameter Help和Quick Info中,我们可以象调用重载函数一样调用它。
最后再说一下Extension Method的结合顺序,它是自左向右结合的,比如x有两个Extension Method方法A,B,x.A().B()将被编绎为B(A(x)).
有人可能会提出疑问,如果是对类进行扩充,那使用继承不就ok了?是的,使用继承确实可以达到扩充功能的目的,但也会带来更多的问题,比如该使用子类的地方,传入的却是父类对象;比如想扩充的类为第三方提供,我们无法得知其内部细节等等。抛开这些统统不谈,为了让一个类型的实例增加一个方法,我们就要使用继承创造自己的子类?这显然不是一个好办法,相对于这些, Extension method给我们提供了一个简便,灵活的解决方案。
我们先来看一个例子。
namespace CSharp
{
class Program
{
static void Main(string[] args)
{
int i = 6;
Console.WriteLine(i.Square());
}
}
static class Extensions
{
public static int Square(this int i)
{
return i * i;
}
}
}
{
class Program
{
static void Main(string[] args)
{
int i = 6;
Console.WriteLine(i.Square());
}
}
static class Extensions
{
public static int Square(this int i)
{
return i * i;
}
}
}
上面的例子中,我们对int的方法进行了扩充,为其增加了Square方法。
下面是编绎后的IL代码。
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 16 (0x10)
.maxstack 1
.locals init ([0] int32 i)
IL_0000: nop
IL_0001: ldc.i4.6
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: call int32 CSharp.Extensions::Square(int32)
IL_0009: call void [mscorlib]System.Console::WriteLine(int32)
IL_000e: nop
IL_000f: ret
} // end of method Program::Main
{
.entrypoint
// Code size 16 (0x10)
.maxstack 1
.locals init ([0] int32 i)
IL_0000: nop
IL_0001: ldc.i4.6
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: call int32 CSharp.Extensions::Square(int32)
IL_0009: call void [mscorlib]System.Console::WriteLine(int32)
IL_000e: nop
IL_000f: ret
} // end of method Program::Main
我们发现原来extension method也没什么玄乎的,编绎器会将其编绎为对Square(i)的调用,原来就是静态类中方法调用。
将Extension Method改为静态方法,并在Main函数中直接调用
namespace CSharp
{
class Program
{
static void Main(string[] args)
{
int i = 6;
Console.WriteLine(Extensions.Square(i));
}
}
static class Extensions
{
public static int Square(this int i)
{
return i * i;
}
}
}
修改后的IL代码{
class Program
{
static void Main(string[] args)
{
int i = 6;
Console.WriteLine(Extensions.Square(i));
}
}
static class Extensions
{
public static int Square(this int i)
{
return i * i;
}
}
}
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 16 (0x10)
.maxstack 1
.locals init ([0] int32 i)
IL_0000: nop
IL_0001: ldc.i4.6
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: call int32 CSharp.Extensions::Square(int32)
IL_0009: call void [mscorlib]System.Console::WriteLine(int32)
IL_000e: nop
IL_000f: ret
} // end of method Program::Main
{
.entrypoint
// Code size 16 (0x10)
.maxstack 1
.locals init ([0] int32 i)
IL_0000: nop
IL_0001: ldc.i4.6
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: call int32 CSharp.Extensions::Square(int32)
IL_0009: call void [mscorlib]System.Console::WriteLine(int32)
IL_000e: nop
IL_000f: ret
} // end of method Program::Main
可以看到,两种代码产生的IL代码一模一样。
下面说下如何定义Extension Method,首先,Extension Method必须定义在普通的 static class内,这里讲的普通是static class不能为泛型(non-generic)或者内嵌(non-nested)的。其次,Extension Method的第一个参数必须为this + 类型 + 变量名。该参数指定了拥有该方法的类型为 this指定的类型。可能有的人会问,即然this指定的类型拥有,那么可不可以使用类型的私有或受保护的成员?很遗憾,不能。记住,Extension Method仅仅是看起来像是一个类型的方法,但其实质上不是,它更像是静态类型的静态方法,事实上,它确实拥有静态方法所具有的所有功能。
说完了这些,再说一下Extension Method的作用域,它是整个namespace可见的,并且可以通过using namespace来导入其它命名空间中的Extension Method. 导入的扩导方法有没有可能因为跟原类型的重名方法冲突而导致编绎失败呢?不会。如果有同名的方法,Extension Method会出现在该方法Intellisense中的Parameter Help和Quick Info中,我们可以象调用重载函数一样调用它。
最后再说一下Extension Method的结合顺序,它是自左向右结合的,比如x有两个Extension Method方法A,B,x.A().B()将被编绎为B(A(x)).