2010-03-21 4 views
17

Diese Fragen beziehen sich auf zwei verschiedene Implementierungen von im Wesentlichen demselben Code.Vorteile/Nachteile verschiedener Implementierungen für den Vergleich von Objekten

Zuerst Delegierten mit einem Vergleichsverfahren zu schaffen, die als Parameter verwendet werden kann, wenn eine Sammlung von Objekten Sortierung:

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

ich die oben verwenden, wenn ich eine Art und Weise des Sortierens eine Sammlung haben wollen Foo Objekte anders als meine CompareTo Funktion bietet. Zum Beispiel:

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

Zweitens mit IComparer:

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

verwende ich die oben, wenn ich eine binäre Suche nach einem Foo Objekt in einer Sammlung von Foo Objekten tun will. Zum Beispiel:

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

Meine Fragen sind:

  • Was sind die Vor- und Nachteile jeder dieser Implementierungen?
  • Was sind weitere Möglichkeiten, um jede dieser Implementierungen zu nutzen?
  • Gibt es eine Möglichkeit, diese Implementierungen so zu kombinieren, dass ich den Code nicht duplizieren muss?
  • Kann ich mit nur einer dieser Implementierungen sowohl eine binäre Suche als auch eine alternative Sammlungssortierung erreichen?

Antwort

5

Wahrscheinlich ist der größte Vorteil, einen Comparison<T> im Gegensatz zu einem IComparer<T> zu akzeptieren, die Fähigkeit, anonyme Methoden zu schreiben. Wenn ich, sagen wir mal, ein List<MyClass>, wo MyClass enthält eine ID Eigenschaft, die für die Sortierung verwendet werden soll, kann ich schreiben:

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

die viel bequemer ist, als eine ganze IComparer<MyClass> Implementierung zu schreiben.

Ich bin nicht sicher, ob die Annahme einer IComparer<T> wirklich große Vorteile hat, außer für die Kompatibilität mit Legacy-Code (einschließlich .NET Framework-Klassen).Die Eigenschaft Comparer<T>.Default ist nur für primitive Typen nützlich. alles andere erfordert normalerweise zusätzliche Arbeit, um dagegen zu programmieren.

Um Code Doppelarbeit zu vermeiden, wenn ich mit IComparer<T> arbeiten müssen, eine Sache, die ich in der Regel tun, ist eine generische comparer zu schaffen, wie folgt aus:

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

Dies ermöglicht das Schreiben von Code wie:

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

Es ist nicht gerade schön, aber es spart Zeit.

Eine weitere nützliche Klasse ich habe, ist diese:

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

, welche Sie für IComparer<T> wie entworfen Code schreiben:

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

Großartige Codebeispiele! –

7

Es gibt wirklich keinen Vorteil für beide Optionen in Bezug auf die Leistung. Es ist wirklich eine Frage der Bequemlichkeit und der Wartbarkeit des Codes. Wählen Sie die gewünschte Option. Davon abgesehen beschränken die Methoden Ihre Entscheidungen leicht.

Sie können die IComparer<T> Schnittstelle für List<T>.Sort verwenden, die es Ihnen ermöglichen würde, Code nicht zu duplizieren.

Leider implementiert BinarySearch keine Option mit Comparison<T>, so dass Sie keinen Comparison<T> Delegaten für diese Methode verwenden können (zumindest nicht direkt).

Wenn Sie wirklich verwenden Comparison<T> für beide wollten, könnten Sie eine generische IComparer<T> Implementierung machen, die einen Comparison<T> Delegierten in seinem Konstruktor nahm und umgesetzt 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

In Ihrem Fall Vorteil der Verwendung eines IComparer<T> über Comparision<T> Delegierten hat, ist, dass Sie es auch für die Sort-Methode verwenden können, so dass Sie nicht überhaupt eine Comparison Delegierten Version benötigen.

Eine andere nützliche Sache, die Sie tun können, ist eine delegierte IComparer<T> Implementierung wie diese Umsetzung:

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

und eine erweiterte Version:

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

Typo: fehlt eine schließende geschweifte Klammer '}' in Zeile 8 der erstes Code-Snippet –

1

Der Delegierte Technik sehr kurz ist (Lambda-Ausdrücke sein könnte noch kürzer), also wenn ein kürzerer Code Ihr Ziel ist, dann ist das ein Vorteil.

Durch die Implementierung von IComparer (und seiner generischen Entsprechung) wird der Code jedoch testbarer: Sie können Ihrer Vergleichsklasse/-methode einige Komponententests hinzufügen.

Außerdem können Sie Ihre Vergleichsimplementierung wiederverwenden, wenn Sie zwei oder mehr Vergleiche erstellen und diese als neue Vergleiche kombinieren. Die Wiederverwendung von Code mit anonymen Delegierten ist schwieriger zu erreichen.

Also, um es zusammenzufassen:

Anonym Die Delegierten: kürzere (und vielleicht sauberer) -Code

Explizite Implementierung: Testbarkeit und die Wiederverwendung von Code.

+1

Ich stimme dem Punkt bezüglich der Wiederverwendung von Code zu, bin jedoch von der Testbarkeit nicht wirklich überzeugt. Warum sollte eine Methode, die einen 'IComparer ' akzeptiert, einfacher zu testen sein als einer, der einen 'Comparison 'akzeptiert? Beide verwenden eine Inversion der Kontrolle. – Aaronaught

+1

@Aaronaught, ich denke, ich missverstanden: ** beide ** explizite Implementierungen sind leicht zu testen ('IComparer ' und 'Vergleich '), im Gegensatz zu anonymen Delegaten, die schwieriger zu testen sind. –

+0

Ah, ich verstehe, Sie beziehen sich auf Unit-Tests der IComparer 'selbst, nicht die Methode, die es akzeptiert. Ich kann mir nicht vorstellen, dass ich wirklich einen davon testen möchte, aber Sie haben recht, es ist definitiv einfacher, Tests zu schreiben, wenn Sie wollen. – Aaronaught

0

Sie richten sich wirklich unterschiedliche Bedürfnisse:

IComparable für Objekte geeignet ist, die bestellt werden. Reale Zahlen sollten vergleichbar sein, aber komplexe Zahlen können nicht - es ist schlecht definiert.

IComparer ermöglicht es, wiederverwendbare, gut eingekapselte Vergleiche zu definieren. Dies ist besonders nützlich, wenn der Vergleich einige zusätzliche Informationen benötigt. Sie können beispielsweise Daten und Uhrzeiten aus verschiedenen Zeitzonen vergleichen. Das kann kompliziert sein, und ein separater Vergleicher sollte für diesen Zweck verwendet werden.

Ein Vergleichsverfahren wird für einfache Vergleichsoperationen erstellt, die nicht kompliziert genug sind, damit die Wiederverwendbarkeit von Bedeutung ist, z.B. Sortieren einer Liste von Kunden nach ihrem Vornamen. Dies ist eine einfache Operation und benötigt daher keine zusätzlichen Daten. Ebenso ist dies dem Objekt nicht inhärent, da die Objekte in keiner Weise natürlich angeordnet sind.

Zuletzt gibt es IEquatable, die wichtig sein können, wenn Ihre Equals Methode nur entscheiden kann, ob zwei Objekte gleich sind oder nicht, aber wenn es keine Vorstellung von "größer" und "kleiner" gibt, z. komplexe Zahlen oder Vektoren im Raum.

Verwandte Themen