从C# 3.0说以人为本(三)—— 扩展方法
还记得我们看了好几遍的LINQ吧,下面这个:
- var numQuery =
- from num in numbers
- where (num % 2) == 0
- select num;
第一次讨论的时候我们讲到它等同于:
- IEnumerable<int> numQuery = numbers.Where((number) => number % 2 == 0);
如果用2.0很熟悉的朋友,会知道Where函数是3.0新增的,同时新增的还有Sum,Count等一系列方法,在MSDN 2008中,我们可以看到这些函数归属于IEnumerable<T>泛型接口成员,同时,他们都属于一个新的方法类型称为扩展方法,新的小图标是一个原来的方法图标边上加入一个向下的小箭头。
我们暂且先不讨论这个,先看看Array,List<T>类,它们都扩展了IEnumerable<T>,同时就具有了这些新增的方法。但是,这些方法是全部通过扩展接口重载了吗?好象不像,因为这些方法都大同小异,如果真的是用每个类重载实现的,代码的重复量就未免太大了。而且,如果是通过扩展接口实现的,又如何能称为“扩展方法”呢?
我们再仔细看看MSDN,在每个IEnumerable<T>泛型接口扩展方法的后面,都有一个解释:(由 Enumerable 定义。)。越来越奇怪了!一个接口的方法由另外一个类定义?!这是什么东西?怎么由一个类定义一个接口的方法?
其实,这就是3.0中新增的扩展函数,扩展函数是一个非常非常有用的东西,它可以通过一个自定义的类扩展其他类的方法。例如:
- class ClassNeedExtensionMethod { }
- static class ClassHasExtensionMethod
- {
- public static void ExtensionMethod(this ClassNeedExtensionMethod cn)
- {
- Console.WriteLine("Extension Method Invoked!");
- }
- }
- class ExtensionMethodTester
- {
- public void Test()
- {
- ClassNeedExtensionMethod cn = new ClassNeedExtensionMethod();
- // 调用扩展方法 方式一
- cn.ExtensionMethod();
- // 调用扩展方法 方式二
- ClassHasExtensionMethod.ExtensionMethod(cn);
- }
- }
ClassNeedExtensionMethod
情况是不是和IEnumerable<T>很像?如果把ClassHasExtensionMethod换成Enumerable,把ClassNeedExtensionMethod换成IEnumerable<T>,是不是就和MSDN所描述的情况完全一致了?如果你高兴,甚至可以扩展Object类的方法,太强大了!
不过扩展函数必须是在一个静态类中,并且自身也必须是一个静态函数,调用方法有两种,在上面的代码中已经用红字标出了。
扩展函数的出现能解决什么情况?不需要通过类继承的方式来继承某些函数了!
首先我们都知道,一个类只能继承自一个类,但是可以扩展无数个(理论上,不过谁有真的会给一个类继承无数接口呢……)接口。如果你发现类A,类B已经继承自类P,但是又要给A B都再继承一个父类C(这种情况是非常常见的,在项目中再普遍不过了),2.0唯一的做法就是:追根溯源,让P继承自C。万一P又已经继承自M,怎么办?最恶劣的情况,P继承自Page!难道你要让Page类继承自你的C类吗?你和微软商量去,这便导致了父类继承的最经典的情况。
但是有了扩展函数,如果只是要继承方法,而不需要继承变量的话,我们只需要扩展一个接口IC,然后帮IC扩展函数,那A B就具有这个已经定义好的函数了,完全不再需要考虑父类继承问题。
最经典的应用场景:0/R Mapping框架 ActiveRecord,本来需要继承父类以具有Save等方法,但是这样等于限制了以后的类继承条件,现在我们可以改良,使用IActiveRecord,然后帮IActiveRecord扩展Save方法,同样能造成相同的使用而不会影响下层代码的改动,而且不再继承自任何父类!
扩展函数,.NET 3.0第三个以人为本的典例。
事实上……小凡觉得微软可能也发现使用LINQ后集合类需要扩展很多方法,但是也出现了类继承问题,于是绞尽脑汁想出了这个扩展函数的解决方案。不管怎么说,这个解决方案很巧妙,的确从根本出发解决了很多问题。