前段时间,我做的一个项目有一个小小的需求,即:对范型集合类型ObservableCollection< T>进行排序。ObservableCollection< >这个类型在WPF和Silverlight中非常有用,因为它实现了INotifyCollectionChanged接口,继而在进行数据绑定的时候,如果将ItemsControl的ItemsSource属性绑定到一个ObservableCollection< T>对象上,那么当这个集合变化的时候(Add, Remove, Insert, Clear等等),相应的ItemsControl也会同步Update。由于ObservableCollection< T>继承自Collection< T>,因此它没有List< T>提供的Sort方法,所以如果我们想对ObservableCollection进行排序的话,就需要自己实现。
继承与扩展方法:两个解决方案
针对这个需求,我想到两个方案解决,方案一就是写一个子类,继承ObservableCollection,进而在其中实现Sort方法,相应的代码如下:
1 public class SortableObservableCollection< T> : ObservableCollection< T> 2 { 3 public void Sort() 4 { 5 Sort(Comparer< T>.Default); 6 } 7 8 public void Sort(IComparer< T> comparer) 9 { 10 int i, j; 11 T index; 12 for (i = 1; i < this.Count; i++) 13 { 14 index = Items[i]; 15 j = i; 16 while ((j > 0) && (comparer.Compare(Items[j - 1], index) == 1)) 17 { 18 Items[j] = Items[j - 1]; 19 j = j - 1; 20 } 21 Items[j] = index; 22 } 23 } 24 }
方案二是为ObservableCollection< T>添加扩展方法,相应的代码如下:
1 public static class ObservableCollectionExtension 2 { 3 public static void Sort< T>(this ObservableCollection< T> Collection) 4 { 5 Collection.Sort(Comparer< T>.Default); 6 } 7 8 public static void Sort< T>(this ObservableCollection< T> Collection, IComparer< T> comparer) 9 { 10 int i, j; 11 T index; 12 for (i = 1; i < Collection.Count; i++) 13 { 14 index = Collection[i]; 15 j = i; 16 while ((j > 0) && (comparer.Compare(Collection[j - 1], index) == 1)) 17 { 18 Collection[j] = Collection[j - 1]; 19 j = j - 1; 20 } 21 Collection[j] = index; 22 } 23 } 24 }
注:以上的代码只是例子,并没有对Argument进行应有的check,也没有考虑运行时是否会出现其他异常。
继承与扩展方法之比较
这两个方案都可以满足需求,那么就需要对它们的进行一下比较以便采取更优秀的方案。结合以上的案例,我主要是以下几点进行比较(当然也有一些并不适用于以上例子,我也一并比较了):
1)针对程序集引用与命名空间合并:
无论是继承还是扩展方法,如果被继承和扩展的类不在我们的程序集中,那么为了使用扩展的新功能,我们就必须引用这个新的程序集,同时我们可以在新的程序集中把命名空间写成和原有类所在的命名空间一样,因此在这一点上,二者是平手。
2)针对sealed类:
由于sealed类不能再被继承,因此在这种情况下我们如果需要为某一个密封类扩展功能,那么只能考虑扩展方法,在这次较量中,扩展方法+1
3)针对public, protected, internal, private成员:
扩展方法其实只是语法糖,这点想必大家都清楚,因此在我们使用扩展方法的时候,是无法获得对象private和protected成员的使用权限的,如果不在一个程序集中,internal也将变为不可见。相比之下,继承的优势在于可以访问到protected成员,因此在此役中继承+1
4)针对多态:
扩展方法只能对类扩展public成员,因此只能够能过方法的overload来实现静态多态特性,虽然通过this关键字传入方法体的对象能够在调用上使用override方法,但是您无永远在扩展方法中实现override一个方法,而继承则可以对基类中的abstract和virtual成员进行override,从而获得动态多态特性,当然overload也成了小儿科。因此在此役中继承+1
5)针对接口和抽象类的扩展方法:
C#3.0多出来的Linq命名空间下为IEnumerable接口添加了N多的扩展方法,虽然如果搞不清楚乱用会造成很多问题,但是它们的确让我们的开发变得简单明了。如果对于接口和抽象类实现扩展方法,那么受益者将会很多,凡是实现了该接口的类都将拥有这些扩展方法,而继承(这里对于接口应该不能称作继承,而是implement)却无法达到这一效果。当然有些功能我们只希望某一个类或者某一些类才有,而不是像扩展方法这样让所有抽象类的子类和实现接口的类都具有这个功能,但是在这次比较中,扩展方法仍然更胜一筹,扩展方法+1
6)针对代码的扩展性和维护性:
有很多时候,代码已经写好才突然出现某一些附加的需求,比如我已经写好了数据绑定的逻辑,用的正是ObservableCollection,如果我需要用继承一个子类扩展Sort方法,那么我将需要更改很多处代码。甚至有些时候代码是别人写的,已经编译成程序集来给你用,你可以在他的代码里得到ObservableCollection对象,却再也无法将这个对象变成SortableObservableCollection使用了。因此在这种情况下,除非反编译已经生成的程序集,不然就只能通过扩展方法实现了。而且如果再有新的需求,我们完全可以再写一个static class加入新的扩展方法,这一点继承一个子类是无法满足需要的。因此,此役中扩展方法+1
7)关于反射:
大家都知道反射在某些时候是一种很方便的手段,或者说是不得不用的手段。在有反射参与的情况下,扩展方法可以说没了用武之地,比如刚才那个ObservableCollection,我得到typeof(ObservableCollection)之后,仍然无法得到Sort方法的MethodInfo,因此也无法Invoke,原因是那个Sort方法是定义在ObservableCollectionExtension这个静态类当中的,如果我想用Sort方法就必须反射ObservableCollectionExtension,这对于使用者是极为不便的。然后对于继承,这个问题就不会出现,我当然能够在SortableObservableCollection的Type中找到Sort方法对应的MethodInfo。因此,在有反射参与的情况下,继承+1
8)关于面向对象:
我一直觉得面向对象是仁者见仁,智者见智的东西,而且不同的场合不同的应用对于两个方案可能会有不同的选择。按照道理说,无疑继承更加面向对象一点,当然这只是我个人的感觉。其实上面提到过扩展方法也能够在方法调用上体现多态,只不过是它无法override方法罢了。因此在这个比较上,继承子类+0.5
通过上面的几项比较,其实继承和扩展方法两者实在是难分伯仲。我觉得在选择的时候还是要具体问题具体分析的,比如上面给出的有关protected和sealed这两个问题,我们就是“不得不”做出选择,而更多的情形还是需要考虑易用性以及代码美观等因素的。比如关于我那个项目,我最终选择的扩展方法,原因就是我懒得去改以前的代码了。
那么对于继承与扩展方法这两种方式对于扩展一个类的的比较就到此为止了,欢迎大家进行补充,并指出我的不足之处。
本文来自wodehuajianrui的博客。
【编辑推荐】