2012-04-27 11 views
8

Ich habe versucht, OrderBy in einer LINQ-Anweisung mit einem anonymen Objekt zu arbeiten, aber jetzt fehlgeschlagen.LINQ OrderBy anonymes Objekt mit Projektionsvergleich

checkte ich diese schon:
Anonymous IComparer implementation
C# linq sort - quick way of instantiating IComparer
How to sort an array of object by a specific field in C#?

verbrachte ich ein paar Stunden verschiedene Ansätze versucht, aber es hat etwas, was ich zu fehlen.

Lasst uns sagen, dass es die folgende Klasse:

public class Product 
{ 
    public int Id {get; set;} 
    public string Name {get; set;} 
    public int Popularity {get; set;} 
    public decimal Price {get; set;} 
} 

Und products ist eine Liste dieser Objekte.

Wie kann ich diese LINQ-Anweisung abschließen, so dass sie mit dem anonymen Objekt funktioniert?
Um klar zu sein, ich weiß, dass ich das auf eine andere Art und Weise tun kann, aber ich wäre sehr interessiert zu lernen, wie dieses spezielle Beispiel funktioniert.

var sortedProducts = products 
         .OrderBy(p => 
           new {p.Popularity, p.Price}, 
           [IComparer magic goes here]); 

Es scheint, dass es mit einer Implementierung der ProjectionComparer möglich sein sollte:
http://code.google.com/p/edulinq/source/browse/src/Edulinq/ProjectionComparer.cs?r=0c583631b709679831c99df2646fc9adb781b2be

Irgendwelche Ideen, wie dies zu tun?

UPDATE:

habe ich einen schnellen Performance-Test auf diese - die anonyme comparer Lösung vs Standard orderby.thenby und es scheint, dass die anonyme Lösung ziemlich langsamer ist was wahrscheinlich ist, was wir vielleicht ohnehin erwartet.

  numProd | Anon | chained orderby clauses 
     10 000 | 47 ms | 31 ms 
     100 000 | 468 ms | 234 ms 
     1 000 000| 5818 ms | 2387 ms 
     5 000 000| 29547 ms| 12105 ms 
+0

was sind Ihre Bestellkriterien? Wollen Sie sagen, dass es zuerst nach Popularität und dann nach Preis sein sollte? Warum nicht OrderBy auf Popularität und dann ThenBy auf Preis? –

+0

@JamesMichaelHare - sagen wir höhere Popularität als primärer Faktor, niedrigerer Preis als sekundärer Faktor, wenn das für das Beispiel einen Unterschied macht. EDIT: Ich weiß, ich kann das tun, aber ich frage mich, ob die Annäherung mit dem anonymen Objekt überhaupt funktioniert, und wenn ja, kann ich einfach nicht herausfinden, wie. –

+1

Warum überhaupt ein anonymes Objekt in OrderBy() verwenden? Warum nicht einfach: 'products.OrderByDescending (p => p.Popularity) .ThenBy (p => p.Price)' –

Antwort

7

Sie können eine IComparer<T> Implementierung machen, die einen Delegaten verwendet, die Sie für den Vergleich liefern, und instanziiert es mit Typinferenz (ähnlich „cast Beispiel vorangehen“):

static class AnonymousComparer 
{ 
    public static IComparer<T> GetComparer<T>(T example, Comparison<T> comparison) 
    { 
     return new ComparerImpl<T>(comparison); 
    } 
    private class ComparerImpl<T> : IComparer<T> 
    { 
     private readonly Comparison<T> _comparison; 
     public ComparerImpl(Comparison<T> comparison) { _comparison = comparison; } 
     public int Compare(T x, T y) { return _comparison.Invoke(x, y); } 
    } 
} 

Und es verwenden, also:

var comparer = AnonymousComparer.GetComparer(
    new { Popularity = 0, Price = 0m }, 
    (a, b) => //comparison logic goes here 
    ); 

var sortedProducts = products 
    .OrderBy(p => 
     new { p.Popularity, p.Price }, 
     comparer); 

EDIT: Ich habe gerade überprüft die Projektion comparer-Seite Sie verknüpft. Bei diesem Ansatz benötigen Sie das Argument "example" für die Typinferenz nicht. Der Ansatz muss jedoch angepasst werden, um statt einer Schnittstelle einen Delegaten zu verwenden. Hier ist es:

//adapted from http://code.google.com/p/edulinq/source/browse/src/Edulinq/ProjectionComparer.cs?r=0c583631b709679831c99df2646fc9adb781b2be 
static class AnonymousProjectionComparer 
{ 
    private class ProjectionComparer<TElement, TKey> : IComparer<TElement> 
    { 
     private readonly Func<TElement, TKey> keySelector; 
     private readonly Comparison<TKey> comparison; 

     internal ProjectionComparer(Func<TElement, TKey> keySelector, Comparison<TKey> comparison) 
     { 
      this.keySelector = keySelector; 
      this.comparison = comparison ?? Comparer<TKey>.Default.Compare; 
     } 

     public int Compare(TElement x, TElement y) 
     { 
      TKey keyX = keySelector(x); 
      TKey keyY = keySelector(y); 
      return comparison.Invoke(keyX, keyY); 
     } 
    } 

    public static IComparer<TElement> GetComparer<TElement, TKey>(Func<TElement, TKey> keySelector, Comparison<TKey> comparison) 
    { 
     return new ProjectionComparer<TElement, TKey>(keySelector, comparison); 
    } 
} 
+0

Brilliant! Ich habe die erste Version überprüft und es funktioniert wie erwartet. Ich werde auch den angepassten ProjectionComparer überprüfen und dann mehr über Cast by Example erfahren. Vielen Dank für Ihre umfassende Antwort! –

4

Sie brauchen nicht wirklich ein anonymes Objekt diese Objekte durch populartiy absteigend und dann Preis zu bestellen, können Sie OrerBy und ThenBy in Kombination verwendet werden, wie:

var sortedProducts = products.OrderByDescending(p => p.Popularity) 
    .ThenBy(p => p.Price); 

ein IComparer<T> zu tun auf ein anonymer Typ, wäre es am besten, wenn Sie eine Factory verwenden, um eine von einem Delegaten zu konstruieren und Typinferenz zu verwenden (das Angeben anonymer Typen ohne Schlussfolgerung ist ein Schmerz!).

Sie könnten die Auswirkungen auf die Leistung der Schaffung anonymen Objekte rein für die Bestellung messen wollen, aber Phoogs Antwort gibt eine gute Möglichkeit Comparison<T> Delegierten zu verwenden, um eine IComparer<T> on the fly zu konstruieren ..

+0

siehe oben - wenn es trivial wäre, würde ich hoffen, dass ich es ohne Hilfe hätte schaffen können. Ich weiß, dass das Problem auf andere Weise gelöst werden kann. –

+0

@Joanna: verstanden, wollte nur sicherstellen, dass es kein Problem war zu wissen, dass OrderBy/ThenBy angekettet werden kann. Phoog hat unten eine großartige Lösung. –

+0

Ich aktualisierte die Ergebnisse einiger kurzer Leistungstests - die anonyme Vergleichslösung ist langsamer als erwartet. Es ist gut zu wissen. –

0

Nicht gerade eine Antwort .. Aber zu lang für einen Kommentar: Es ist schwer, vernünftige generische Vergleiche zu erstellen.

Während es eine gut etablierte Vergleichsbeziehung für Objekte durch einzelne Eigenschaft gibt, gibt es keine solche für mehrere oder sogar 2 Eigenschaften. I.e. Das ist ein sehr häufiges Problem, wenn Sie versuchen, Punkte auf einer flachen Oberfläche zu ordnen: nur 2 Werte (x, y), aber es gibt keine Möglichkeit zu sagen (x1, y1) < (x2, y2), damit alle damit einverstanden sind.

In den meisten Fällen sagen Sie am Ende die Reihenfolge nach Attribut 1, dann nach Attribut 2, ... oder indem Sie alle Attribute einem einzelnen Wert zuordnen (d. H. Indem Sie einfach alle multiplizieren).Diese Ansätze sind einfach und ohne Notwendigkeit von generischem Vergleich in LINQ ausgedrückt:

  • Bestellung durch Attribute mit verketteten SortiertNach (attr1) .OrderBy (attr2) ....
  • Bestellung von metrischem SortiertNach (attr1 * attr2) (oder jede andere Metric auf Ihre Objekte)
+1

Was mich interessiert, ist im Grunde die Umsetzung dieser Sache: http://code.google.com/p/edulinq/source/browse/src/Edulinq/ProjectionComparer.cs?r=0c583631b709679831c99df2646fc9adb781b2be. Ich nehme an, wenn ich das richtig verstehe, würde der Rest einfach funktionieren, aber ich würde genau diese Hilfe brauchen. –