比较对象的不同实现的优点/缺点

这个问题涉及基本相同代码的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.Default属性仅对基本类型有用; 其他一切通常需要额外的工作来编码。

为了避免代码重复,当我需要使用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.SortIComparer接口,这将允许您不重复代码。

遗憾的是, 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))) }