2010-03-21 125 views
17

这个问题涉及基本上相同的代码的2个不同的实现。比较对象的不同实现的优点/缺点

首先,使用委托创建一个比较方法,可以分选对象的集合时,可以用作一个参数:

class Foo 
{ 
    public static Comparison<Foo> BarComparison = delegate(Foo foo1, Foo foo2) 
    { 
     return foo1.Bar.CompareTo(foo2.Bar); 
    }; 
} 

我使用以上时,我想有排序的集合的一种方式Foo对象的方式与我的CompareTo函数不同。例如:

List<Foo> fooList = new List<Foo>(); 
fooList.Sort(BarComparison); 

其次,使用的IComparer:

public class BarComparer : IComparer<Foo> 
{ 
    public int Compare(Foo foo1, Foo foo2) 
    { 
     return foo1.Bar.CompareTo(foo2.Bar); 
    } 
} 

我用上面的时候我希望做的Foo对象的集合在一个Foo对象二进制搜索。例如:

BarComparer comparer = new BarComparer(); 
List<Foo> fooList = new List<Foo>(); 
Foo foo = new Foo(); 
int index = fooList.BinarySearch(foo, comparer); 

我的问题是:

  • 各有什么这些实现的优点和缺点?
  • 有什么更多的方式来利用每个这些实现?
  • 有没有一种方法可以将这些实现组合起来,使得我不需要复制代码?
  • 我是否可以仅使用其中一种实现来实现二分搜索和另一种集合排序?

回答

5

接受Comparison<T>而不是IComparer<T>的最大好处是可以编写匿名方法。如果我有,比方说,一个List<MyClass>,其中MyClass包含应该用于排序的ID财产,我可以这样写:

myList.Sort((c1, c2) => c1.ID.CompareTo(c2.ID)); 

这是很多不必编写整个IComparer<MyClass>实现更方便。

我不确定接受IComparer<T>真的有什么主要优点,除了与遗留代码(包括.NET Framework类)的兼容性。Comparer<T>.Default属性仅对基元类型非常有用;其他一切通常需要额外的工作来进行编码。

为了避免重复代码,当我需要IComparer<T>工作,有一件事我通常做的是创建一个通用的比较器,像这样:

public class AnonymousComparer<T> : IComparer<T> 
{ 
    private Comparison<T> comparison; 

    public AnonymousComparer(Comparison<T> 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<MyClass>(x.ID.CompareTo(y.ID))); 

这不完全漂亮,但它节省了一些时间。

另一个有用的我已经是这一个:

public class PropertyComparer<T, TProp> : IComparer<T> 
    where TProp : IComparable 
{ 
    private Func<T, TProp> func; 

    public PropertyComparer(Func<T, TProp> 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<T>为代码:

myList.BinarySearch(item, new PropertyComparer<MyClass, int>(c => c.ID)); 
+0

伟大的代码示例! – 2010-03-21 19:23:49

7

在性能方面,两种方案都没有优势。这确实是一个方便和代码可维护性的问题。选择你喜欢的选项。这就是说,这些方法限制了你的选择。

您可以使用IComparer<T>接口List<T>.Sort,这将允许您不重复的代码。

不幸的是,BinarySearch没有使用Comparison<T>来实现一个选项,因此您不能为该方法使用Comparison<T>委托(至少不是直接)。

如果你真的想为Comparison<T>这两个,你可以做一个通用的IComparer<T>实现,它的构造函数采用Comparison<T>委托,并实现了IComparer<T>

public class ComparisonComparer<T> : IComparer<T> 
{ 
    private Comparison<T> method; 
    public ComparisonComparer(Comparison<T> comparison) 
    { 
     this.method = comparison; 
    } 

    public int Compare(T arg1, T arg2) 
    { 
     return method(arg1, arg2); 
    } 
} 
0

在你的情况下,利用具有IComparer<T>超过Comparision<T>代表的,是你也可以用它的排序方法,所以你并不需要一个Comparison代表的版本都没有。

你可以做的另一种有用的东西正在实施一项委托IComparer<T>实现这样的:

public class DelegatedComparer<T> : IComparer<T> 
{ 
    Func<T,T,int> _comparision; 
    public DelegatedComparer(Func<T,T,int> comparision) 
    { 
    _comparision = comparision; 
    } 
    public int Compare(T a,T b) { return _comparision(a,b); } 
} 

list.Sort(new DelegatedComparer<Foo>((foo1,foo2)=>foo1.Bar.CompareTo(foo2.Bar)); 

和更先进的版本:

public class PropertyDelegatorComparer<TSource,TProjected> : DelegatedComparer<TSource> 
{ 
    PropertyDelegatorComparer(Func<TSource,TProjected> projection) 
    : base((a,b)=>projection(a).CompareTo(projection(b))) 
} 
+0

错字:在第8行缺少一个右花括号'}'第一个代码片段。 – 2010-03-21 20:06:31

1

委托技术非​​常短(lambda表达式可能甚至更短),所以如果更短的代码是你的目标,那么这是一个优势。

但是,实现IComparer(及其泛型等价物)使您的代码更具可测试性:您可以为比较类/方法添加一些单元测试。

此外,您可以在编写两个或更多比较器时重新使用比较器实现,并将它们组合为新的比较器。使用匿名代理重复使用代码很难实现。

所以,总结一下:

匿名委托:短(也许更清洁)代码

显式实现:可测试性和代码重用。

+1

我同意代码重用的观点,但是我对可测试性并不十分确信。为什么要接受一个'的IComparer 的方法'是任何容易超过一个接受'比较'测试?他们都采用控制倒置。 – Aaronaught 2010-03-21 19:22:21

+1

@Aaronaught,我想我误解:** **都明确实现易于测试('的Icomparable '和'比较'),不像匿名委托,这是很难测试。 – 2010-03-21 19:35:28

+0

啊,我明白了,你指的是单元测试的IComparer''本身,而不是接受它的方法。无法想象实际上想要单元测试其中之一,但是你是对的,如果你想要的话,写测试肯定​​更容易。 – Aaronaught 2010-03-21 19:53:08

0

他们真正满足不同的需要:

IComparable是针对排序的对象很有用。实数应该是可比的,但复数不能 - 它是不明确的。

IComparer允许定义可重复使用,封装良好的比较器。如果比较需要知道一些附加信息,这是特别有用的。例如,您可能想比较不同时区的日期和时间。这可能很复杂,应该为此使用单独的比较器。

比较方法是针对简单的比较操作进行的,这些比较操作对于可重用性来说不够复杂以至于没有任何问题。按名字排序客户列表。这是简单的操作,因此不需要额外的数据。同样,这不是对象固有的,因为对象不是以任何方式自然排序的。

最后,有IEquatable,如果您的Equals方法只能确定两个对象是否相等,但如果没有“较大”和“较小”的概念,则可能很重要。复数或空间中的矢量。