这个问题似乎已经讨论过很多次了,就当是给自己做笔记,我再总结一次。
问题背景是我在给IEnumerable<T>添加一个扩展方法时,要判断两个元素是否相等,假设分别是left和right,类型为T,T没有任何约束。首先自然而然地写出了这样的代码:
更自然地,我得到了一个编译错误,因为类型T上根本没有定义"=="运算符。
后来在网上看到,很多人在这个时候会采用添加约束"where T: IComparable<T>"的方法(称为方案1)。这是一个解决方案,但明显太cuo了。首先,大多数类都不实现IComparable<T>接口;其次,那些实现了IComparable接口的怎么办?而且实际上我根本不需要比较大小,只是比较是否相等,更进一步,我只需要判断对象是否是同一个,即是否是ReferenceEquals的。由此我们可以想到,object类实现了一个静态Equals (obj, obj)方法,对于引用类型,它相当于ReferenceEquals方法,对于值类型,它会进行按位比较。这就是所谓“默认的比较方式”。于是最终代码如下:
但在当时我没想到那么多,我用了一个比较ws的方式,我把比较操作外包出去了:
然后在调用方:
这是方案3.
此外,参考List<T>.Sort()方法的实现,可以知道有个Comparer<T>.Default的默认比较器,其上有个Compare方法,于是得到方案4:
三种方案比较,显然方案1需要添加约束,太不灵活;方案2可以应付大多数常规情况,编码比较方便,但如果需要比较大小则无法实现,这时可以考虑方案4,但由于实现了默认比较器的类型实在很少,方案4也是个鸡肋;方案3则最灵活,可以由方法用户自行指定比较方案。可谓2、3皆有所长,可以作为重载都写上。事实上,方案2可以实现为对方案3的调用:
那能不能再进一步,在C#4.0中可选参数的支持下,不用重载,只写一个方法就行了呢?
实验结果是不行,因为可选参数的默认值必须是编译时常量,而一个方法的引用(其实应该是函数地址吧)在编译时是无法确定的。