比较对象的不同实现的优点/缺点
这个问题涉及基本相同代码的2种不同实现。
首先,使用委托创建一个比较方法,该方法可以在排序对象集合时用作参数:
class Foo { public static Comparison BarComparison = delegate(Foo foo1, Foo foo2) { return foo1.Bar.CompareTo(foo2.Bar); }; }
当我想要一种以与CompareTo函数提供的方式不同的方式对Foo对象集合进行排序时,我使用上述方法。 例如:
List fooList = new List(); fooList.Sort(BarComparison);
二,使用IComparer:
public class BarComparer : IComparer { public int Compare(Foo foo1, Foo foo2) { return foo1.Bar.CompareTo(foo2.Bar); } }
当我想在Foo对象的集合中对Foo对象进行二进制搜索时,我使用上面的代码。 例如:
BarComparer comparer = new BarComparer(); List fooList = new List(); Foo foo = new Foo(); int index = fooList.BinarySearch(foo, comparer);
我的问题是:
- 每种实现的优点和缺点是什么?
- 有哪些方法可以利用这些实现中的每一种?
- 有没有办法以这种方式组合这些实现,我不需要复制代码?
- 我是否可以仅使用其中一个实现来实现二分搜索和替代集合排序?
接受Comparison
而不是IComparer
的最大优势可能是编写匿名方法的能力。 如果我有一个List
,其中MyClass
包含一个应该用于排序的ID
属性,我可以这样写:
myList.Sort((c1, c2) => c1.ID.CompareTo(c2.ID));
这比编写整个IComparer
实现要方便得多。
我不确定接受IComparer
确实有任何主要优点,除了与遗留代码(包括.NET Framework类)的兼容性。 Comparer
属性仅对基本类型有用; 其他一切通常需要额外的工作来编码。
为了避免代码重复,当我需要使用IComparer
,我通常做的一件事就是创建一个通用的比较器,如下所示:
public class AnonymousComparer : IComparer { private Comparison comparison; public AnonymousComparer(Comparison comparison) { if (comparison == null) throw new ArgumentNullException("comparison"); this.comparison = comparison; } public int Compare(T x, T y) { return comparison(x, y); } }
这允许编写如下代码:
myList.BinarySearch(item, new AnonymousComparer(x.ID.CompareTo(y.ID)));
它不是很漂亮,但它节省了一些时间。
我有另一个有用的课程是这个:
public class PropertyComparer : IComparer where TProp : IComparable { private Func func; public PropertyComparer(Func func) { if (func == null) throw new ArgumentNullException("func"); this.func = func; } public int Compare(T x, T y) { TProp px = func(x); TProp py = func(y); return px.CompareTo(py); } }
您可以编写为IComparer
设计的代码:
myList.BinarySearch(item, new PropertyComparer(c => c.ID));
在性能方面,这两种选择都没有任何优势。 这真的是一个方便和代码可维护性的问题。 选择您喜欢的选项。 话虽如此,有问题的方法会略微限制您的选择。
您可以使用List
的IComparer
接口,这将允许您不重复代码。
遗憾的是, BinarySearch没有使用Comparison
实现选项,因此您不能对该方法使用Comparison
委托(至少不能直接使用)。
如果你真的想对两者使用Comparison
,你可以创建一个通用的IComparer
实现,它在构造函数中使用了Comparison
委托,并实现了IComparer
。
public class ComparisonComparer : IComparer { private Comparison method; public ComparisonComparer(Comparison comparison) { this.method = comparison; } public int Compare(T arg1, T arg2) { return method(arg1, arg2); } }
委托技术非常短(lambda表达式可能更短),所以如果更短的代码是你的目标,那么这是一个优势。
但是,实现IComparer(及其通用等价物)会使您的代码更易于测试:您可以在比较类/方法中添加一些unit testing。
此外,您可以在编写两个或更多比较器时将其重用为比较器实现,并将它们组合为新的比较器。 使用匿名委托重用代码更难实现。
所以,总结一下:
匿名代表 :更短(也许更干净)的代码
显式实现 :可测试性和代码重用。
他们真正满足不同的需求:
IComparable
对于有序的对象很有用。 实数应具有可比性,但复数不能 – 它是不明确的。
IComparer
允许定义可重用,封装良好的比较器。 如果比较需要了解一些其他信息,这将特别有用。 例如,您可能希望比较不同时区的日期和时间。 这可能很复杂,应该使用单独的比较器来实现此目的。
比较方法用于简单的比较操作,这些操作对于可重用性而言不够复杂,例如按其名字对客户列表进行排序。 这是简单的操作,因此不需要额外的数据。 同样,这不是对象固有的,因为对象不是以任何方式自然排序的。
最后,有IEquatable
,如果你的Equals
方法只能决定两个对象是否相等,但是如果没有“更大”和“更小”的概念,例如复数或空间中的向量,这可能很重要。
在您的情况下,使用IComparer
不是Comparision
委托的优点是您也可以将它用于Sort方法,因此您根本不需要Comparison
委托版本。
您可以做的另一个有用的事情是实现委托的IComparer
实现,如下所示:
public class DelegatedComparer : IComparer { Func _comparision; public DelegatedComparer(Func comparision) { _comparision = comparision; } public int Compare(T a,T b) { return _comparision(a,b); } } list.Sort(new DelegatedComparer((foo1,foo2)=>foo1.Bar.CompareTo(foo2.Bar));
还有一个更高级的版本:
public class PropertyDelegatorComparer : DelegatedComparer { PropertyDelegatorComparer(Func projection) : base((a,b)=>projection(a).CompareTo(projection(b))) }