2010-03-21 2 views
17

Cette question implique 2 différentes implémentations de essentiellement le même code.avantages/inconvénients de différentes implémentations pour comparer les objets

Tout d'abord, en utilisant délégué pour créer une méthode de comparaison qui peut être utilisé comme paramètre lors du tri d'une collection d'objets:

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

-je utiliser ce qui précède quand je veux avoir une façon de trier une collection de Foo objets d'une manière différente que mes offres de fonction CompareTo. Par exemple:

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

En second lieu, en utilisant IComparer:

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

J'utilise ci-dessus quand je veux faire une recherche binaire pour un objet Foo dans une collection d'objets Foo. Par exemple:

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

Mes questions sont les suivantes:

  • Quels sont les avantages et les inconvénients de chacune de ces mises en œuvre?
  • Quelles sont les autres façons de tirer parti de chacune de ces implémentations?
  • Est-il possible de combiner ces mises en œuvre de telle sorte que je ne ai pas besoin de dupliquer le code?
  • Puis-je obtenir à la fois une recherche binaire et une collection alternatif trier par seulement 1 de ces mises en œuvre?

Répondre

5

Probablement le plus grand avantage à accepter un Comparison<T> par opposition à un IComparer<T> est la possibilité d'écrire des méthodes anonymes. Si je l'ai, disons, un List<MyClass>, où MyClass contient une propriété ID qui doit être utilisé pour le tri, je peux écrire:

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

Ce qui est beaucoup plus pratique que d'avoir à écrire une implémentation IComparer<MyClass> ensemble.

Je ne suis pas sûr qu'accepter un IComparer<T> a vraiment des avantages majeurs, sauf pour la compatibilité avec le code existant (y compris les classes .NET Framework).La propriété Comparer<T>.Default n'est vraiment utile que pour les types primitifs; tout le reste nécessite généralement du travail supplémentaire pour le code.

Pour éviter la duplication de code quand je dois travailler avec IComparer<T>, une chose que je fais habituellement est de créer un comparateur générique, comme ceci:

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); 
    } 
} 

Cela permet d'écrire du code tel que:

myList.BinarySearch(item, 
    new AnonymousComparer<MyClass>(x.ID.CompareTo(y.ID))); 

Ce n'est pas vraiment joli, mais cela fait gagner du temps.

Une autre classe utile que j'ai est celle-ci:

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); 
    } 
} 

Que vous pouvez écrire un code conçu pour IComparer<T> comme:

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

De bons exemples de code! –

7

Il n'y a vraiment aucun avantage à l'une des options en termes de performance. C'est vraiment une question de commodité et de maintenabilité du code. Choisissez l'option que vous préférez. Cela étant dit, les méthodes en question limitent légèrement vos choix.

Vous pouvez utiliser l'interface IComparer<T> pour List<T>.Sort, ce qui vous permettrait de ne pas dupliquer le code.

Malheureusement, BinarySearch n'implémente pas une option utilisant un Comparison<T>, donc vous ne pouvez pas utiliser un délégué Comparison<T> pour cette méthode (du moins pas directement).

Si vous voulez vraiment utiliser Comparison<T> pour les deux, vous pourriez faire une IComparer<T> générique mise en œuvre qui a pris un délégué Comparison<T> dans son constructeur, et mis en œuvre 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

Dans votre cas, l'avantage d'avoir un IComparer<T> sur Comparision<T> délégué, est que vous pouvez également l'utiliser pour la méthode de tri, de sorte que vous n'avez pas besoin d'une version de délégué Comparison du tout.

Une autre chose utile que vous pouvez faire met en œuvre un IComparer<T> délégué la mise en œuvre comme ceci:

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)); 

et une version plus avancée:

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

Typo: manque une accolade fermante '}' à la ligne 8 premier extrait de code. –

1

La technique de délégué est très courte (expressions lambda peut être encore plus court), donc si un code plus court est votre objectif, alors c'est un avantage. Toutefois, l'implémentation de IComparer (et de son équivalent générique) rend votre code plus testable: vous pouvez ajouter des tests unitaires à votre classe/méthode de comparaison. En outre, vous pouvez réutiliser votre implémentation lors de la composition de deux comparateurs ou plus et les combiner en tant que nouveau comparateur. La réutilisation de code avec des délégués anonymes est plus difficile à réaliser.

Donc, pour résumer:

Anonyme Les délégués: plus court (et peut-être plus propre) Code

explicite la mise en œuvre: testabilité et réutilisation du code.

+1

Je suis d'accord sur le point sur la réutilisation du code, mais je ne suis pas vraiment convaincu de la testabilité. Pourquoi une méthode d'accepter une '' IComparer être plus facile à tester que l'un d'accepter un '' comparaison ? Ils utilisent tous les deux l'inversion du contrôle. – Aaronaught

+1

@Aaronaught, je pense que je suis mal compris: ** les deux ** implémentations explicites sont faciles à tester ('IComparer ' et 'Comparaison '), contrairement aux délégués anonymes, qui sont plus difficiles à tester. –

+0

Ah, je comprends, vous faites référence à l'unité-test lui-même '' IComparer , pas la méthode qui l'accepte. Je ne peux pas m'imaginer réellement vouloir tester un de ces éléments, mais vous avez raison, il est certainement plus facile d'écrire des tests contre si vous le souhaitez. – Aaronaught

0

Ils traitent vraiment différents besoins:

IComparable est utile pour les objets qui sont commandés. Les nombres réels devraient être comparables, mais les nombres complexes ne peuvent pas - c'est mal défini.

IComparer permet de définir des comparateurs réutilisables et bien encapsulés. Ceci est particulièrement utile si la comparaison nécessite de connaître des informations supplémentaires. Par exemple, vous pouvez comparer les dates et heures de différents fuseaux horaires. Cela peut être compliqué, et un comparateur doit être utilisé à cette fin.

Une méthode de comparaison est réalisée pour des opérations de comparaison simples qui ne sont pas suffisamment compliquées pour que la réutilisabilité soit un problème, par ex. trier une liste de clients par leur prénom. C'est une opération simple, donc n'a pas besoin de données supplémentaires. De même, ceci n'est pas inhérent à l'objet, car les objets ne sont pas naturellement ordonnés de quelque façon que ce soit.

Enfin, il y a IEquatable, ce qui peut être important si votre méthode Equals peut seulement décider si deux objets sont égaux ou non, mais s'il n'y a pas de notion de 'plus grand' et 'plus petit', par ex. nombres complexes ou vecteurs dans l'espace.

Questions connexes