对泛型类型进行判等(比较)

这个问题似乎已经讨论过很多次了,就当是给自己做笔记,我再总结一次。

问题背景是我在给IEnumerable<T>添加一个扩展方法时,要判断两个元素是否相等,假设分别是left和right,类型为T,T没有任何约束。首先自然而然地写出了这样的代码:

if (left == right) // <-- Compile error
{

}

更自然地,我得到了一个编译错误,因为类型T上根本没有定义"=="运算符。

后来在网上看到,很多人在这个时候会采用添加约束"where T: IComparable<T>"的方法(称为方案1)。这是一个解决方案,但明显太cuo了。首先,大多数类都不实现IComparable<T>接口;其次,那些实现了IComparable接口的怎么办?而且实际上我根本不需要比较大小,只是比较是否相等,更进一步,我只需要判断对象是否是同一个,即是否是ReferenceEquals的。由此我们可以想到,object类实现了一个静态Equals (obj, obj)方法,对于引用类型,它相当于ReferenceEquals方法,对于值类型,它会进行按位比较。这就是所谓“默认的比较方式”。于是最终代码如下:

if (object.Equals (left, right)) // Solution 2.
{

}
这是方案2.

但在当时我没想到那么多,我用了一个比较ws的方式,我把比较操作外包出去了:

 

public IEnumerable<T> DemoMethod<T> (this IEnumerable<T> source,
                      Func
<T, T, bool> equalityComparison)
{

    
if (equalityComparison (left, right))
    {
    
    }

}

然后在调用方:

foreach (var item in "abc".DemoMethod ((x, y) => x == y)) 
//string实现IEnumerable<char>接口,而char支持"=="操作符
{

}

这是方案3.

此外,参考List<T>.Sort()方法的实现,可以知道有个Comparer<T>.Default的默认比较器,其上有个Compare方法,于是得到方案4:

if (Comparer<T>.Default.Compare (left, right) == 0)
{

}
然而当无法取得所比较类型的默认比较器时,在Compare方法上会引发ArgumentException异常。

三种方案比较,显然方案1需要添加约束,太不灵活;方案2可以应付大多数常规情况,编码比较方便,但如果需要比较大小则无法实现,这时可以考虑方案4,但由于实现了默认比较器的类型实在很少,方案4也是个鸡肋;方案3则最灵活,可以由方法用户自行指定比较方案。可谓2、3皆有所长,可以作为重载都写上。事实上,方案2可以实现为对方案3的调用:

public IEnumerable<T> DemoMethod<T> (this IEnumerable<T> source)
{
    source.DemoMethod (
object.Equals);
}

 

那能不能再进一步,在C#4.0中可选参数的支持下,不用重载,只写一个方法就行了呢?

public IEnumerable<T> DemoMethod<T> (this IEnumerable<T> source,
                      Func
<T, T, bool> equalityComparison = object.Equals)
// Compile error: Default parameter value for 'equalityComparison' must be a compile-time constant

实验结果是不行,因为可选参数的默认值必须是编译时常量,而一个方法的引用(其实应该是函数地址吧)在编译时是无法确定的。

posted on 2009-09-05 01:39  Gildor Wang  阅读(1061)  评论(0编辑  收藏  举报

导航