令人激动的“扩展”
【IT168 技术文档】“扩展方法”是C#3.0中的一个新特性之一。他的官方解释是在实例基础上扩展现有的类型方法,说白了就是我们现在可以往现有的类中添加自己的方法,例如我们可以在System.String类中添加一个我们自己定义的Print()方法,是不是很酷呢?:)
1. 为什么我们要使用扩展方法
也许有人会认为这是一个很愚蠢的想法,因为你完全可以在自己的类中定义一个新的方法或是使用继承来实现相同的功能。下面我们来看一看这两种方法会产生的问题:
首先看第一种方法,我们在自己的类中定义一个新的方法去实现扩展功能。比如现在我们正在开发一套游戏程序,其中引用了一个第三方的程序包,而我们要对他的一个类Frozen Sword(冰霜之剑,这个看名字大家也知道是干嘛的吧) 进行一些扩展,比如我们需要它有自动冷却的功能,于是我们定义了AutoCooling(int seconds)方法, 当你使用它时,你会考虑使用下面两种方式中的哪一种呢?
很显然你会选择第一种方法来表达这种意图,因为它最符合我们思考事物的逻辑,剑的冷却是它自身的一个功能,而不是我们使用任何外力作用使它冷却。但这儿我们就会遇到一个问题,由于Frozen Sword是包含在第三方的类库中,我们无法直接去修改他的代码,使我们能以第一种形式取调用AutoCooling方法。Void UseSwordCool(ForzenSword sword)
{
//让剑在5s中冷却下来吧
Sword.AutoCooling(5);
}
Void UseSwordCool(FrozenSword sword)
{
//让剑在5s中冷却下来吧
AutoCooling(sword);
}
这时候很多人都会想到我们使用第二种方法即使用继承来解决这个问题:
当然我们在后面的程序中都要把ForzenSword改成MyFrozenSword才能正常调用。那么使用继承的方法也会有这两个问题:首先继承无法应用在sealed类型的class上,最明显的例子就是 System.String类型,由于string类型是密封类,所以我们无法对它进行继承扩展。其次使用这种方式扩展仍然不能使定义为FrozenSword类型的实例对象使用AutoCooling方法,所以它并不是一个很好的表达。Public class MyFrozenSword : FrozenSword
{
Public void AutoCooling(int seconds) { … }
}
我们写程序的目的就是为了更好的表达现实世界中的复杂逻辑,让code运行起来就和它看起来一样(The code that does what it looks like.)这是我们的设计目标。所以如果是第一种方式最能表达我们的意图,我们就应当使用它,即使它需要我们敲入更多的代码。我承认使用定义新方法或是使用继承在某些环境下确实是正确的,但大部分情况下向现有类型中添加自己的扩展方法才是最完美的解决方案。
2. 如何实现扩展方法
下面我们就来看一看C#3.0中扩展方法是如何实现的。
static class Program
{
static void Main(string[] args)
{
//这里的Game命名空间定义在第三方的类库中
Game.ForzenSword sword = new ForzenSword();
sword.AutoCooling(5);
}
//参数中的this指明是哪一个类的扩展方法
static void AutoCooling(this ForzenSword sword, int seconds)
{
Console.WriteLine(string.Format("ForzenSword will cool down in {0} seconds", seconds));
}
}
这里我们可以看到,首先把我们要扩展的方法定义到一个静态类中,这里为了演示方便,我把它定义在了static class Program即我们的Main函数所在的类中;然后我们定义这个方法的第一个参数为类型的实例,并在其前面加上‘this’表示这是扩展到FrozenSword类型中的方法,这样我们就成功扩展了FrozenSword 类型, 大家可以看到我在Main函数中对该方法的调用,是不是很简单呢:)
3. 扩展方法会带来的问题
当我们大家在惊叹扩展方法给我们带来的灵活性的时候,我们也有一丝疑问,这样会不会破坏原有程序集的封装性呢?答案当然是否定的,但是确实会造成效能上的一些问题和隐患。我们看下面一个例子:
namespace System
{
public static class BadClass
{
public static void DoSomethingEvil(this object s)
{
//对s做点坏事...
}
}
}
因为几乎所有的C#代码都会引用到System这个命名空间,而我们定义的这个方法又是对object类型的扩展,所以我们几乎可以认为这个扩展方法被定义成了一个全局的函数!这就让我们思考它的效能问题,上面的例子运行的实际效果将会使每一个引用类型都加载DoSomethingEvil这个方法(因为C#里引用类型都是object类的子类),这是相当大的资源消耗!当然我们也有解决的办法,最经典的莫过于System.Xml.Linq和System.Data.Linq,LINQ也是C#3.0的一个重要特性,不熟悉的读者可以参考msdn相关文章,LINQ将对Xml文档的扩展方法包和对数据库的扩展方法包分开,使得我们可以有选择的加载,这样可以大大减少我们加载的扩展方法的数量。实际上我们自己在定义扩展方法时也应该分门别类的组织起来,而不应该定义在同一个namespace中加大系统加载的负担。
4. 扩展方法是如何实现的?
下面我们来看一下扩展方法是如何通过编译器来实现的,我把上面程序编译后生成的IL代码打印如下,相信大家从这段代码中就可以简单了解到C#3.0编译器是如何实现扩展方法的。
看IL_0009这行IL代码,我们在程序中调用的是扩展方法AutoCooling, 编译器在遇到这个方式时发现ForzenSword类型并不包含这个方法,于是编译器从引用的数据集中找到静态类Program中的静态方法AutoCooling, 调用之即可。.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 16 (0x10)
.maxstack 2
.locals init ([0] class Game.ForzenSword sword)
IL_0000: nop
IL_0001: newobj instance void Game.ForzenSword::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldc.i4.5
//下面这行代码我们调用了扩展方法AutoCooling
IL_0009: call void ConsoleApplication8.Program::AutoCooling(class Game.ForzenSword,
int32)
IL_000e: nop
IL_000f: ret
} // end of method Program::Main
通过上面的介绍,相信读者朋友们对于微软的这项新技术已经有了一个大致的了解,目前微软在扩展方法方向上最大的应用就是LINQ语言了,有兴趣的读者可以进一步去挖掘其中更深一层的内容,另外,扩展方法在系统构架方面也有着很大的作用,赶紧来体验一下吧:)。
0
相关文章
Void UseSwordCool(ForzenSword sword)