2012-04-27 16 views
8

He intentado obtener OrderBy en una instrucción LINQ para trabajar con un objeto anónimo, pero ahora no lo he logrado.Orden LINQPor objeto anónimo con comparador de proyección

yo nos registramos estos ya:
Anonymous IComparer implementation
C# linq sort - quick way of instantiating IComparer
How to sort an array of object by a specific field in C#?

Me pasé un par de horas probando diferentes enfoques, pero tiene que ser algo que me falta.

Digamos que es la siguiente clase:

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

Y products es una lista de estos objetos.

¿Cómo puedo completar esta declaración LINQ para que funcione con el objeto anónimo?
Para ser claro, sé que puedo hacer esto de otra manera, pero estaría muy interesado en aprender cómo hacer que este ejemplo en particular funcione.

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

Parece que debería ser posible con una implementación de la ProjectionComparer:
http://code.google.com/p/edulinq/source/browse/src/Edulinq/ProjectionComparer.cs?r=0c583631b709679831c99df2646fc9adb781b2be

Alguna idea de cómo hacer esto?

ACTUALIZACIÓN:

Me hicieron una prueba de rendimiento rápido en esto - la solución comparador anónima vs orderby.thenby estándar y parece que la solución anónima es bastante más lenta que es probablemente lo que podríamos haber esperado de todos modos.

  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

¿Cuál es su criterio de pedido? ¿Estás diciendo que debería ser primero por Popularidad, luego por precio? ¿Por qué no hacer OrderBy en popularidad y luego ThenBy en el precio? –

+0

@JamesMichaelHare - digamos una mayor popularidad como factor principal, un precio más bajo como factor secundario si eso marca alguna diferencia en el ejemplo. EDITAR: Sé que puedo hacer esto, pero me pregunto si el enfoque con el objeto anónimo funcionará en absoluto, y si es así, no puedo entender cómo. –

+1

¿Por qué utilizar un objeto anónimo en absoluto en el OrderBy() entonces? ¿Por qué no solo: 'products.OrderByDescending (p => p.Popularity) .ThenBy (p => p.Price)' –

Respuesta

7

usted puede hacer una aplicación IComparer<T> que utiliza un delegado que se proporciona para la comparación, y cree una instancia con la inferencia de tipos (similar a "proyectada por ejemplo"):

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

y utilizarlo así:

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: Acabo de comprobar la página del comparador de proyección se ha vinculado a. Con ese enfoque, no necesita el argumento "ejemplo" para la inferencia de tipo. Sin embargo, el enfoque aún necesita ser adaptado para tomar un delegado en lugar de una interfaz. Aquí está:

//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

¡Brillante! Revisé la primera versión y funciona como se esperaba. Comprobaré también el ProjectionComparer adaptado y luego aprenderé más sobre cast por ejemplo. Muchas gracias por su respuesta integral! –

4

Usted realmente no necesita un objeto anónimo para ordenar estos objetos por populartiy descendente y entonces el precio, puede utilizar OrerBy y ThenBy en combinación, como:

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

Para hacer una IComparer<T> en de forma anónima, sería mejor que utilizara una fábrica para construir una desde un delegado y usar la inferencia de tipo (especificar tipos anónimos sin inferencia es una molestia).

Es posible que desee medir las consecuencias en el rendimiento de la creación de objetos anónimos puramente para el pedido, pero Phoogs respuesta da una buena manera de utilizar Comparison<T> delegado para construir un IComparer<T> sobre la marcha ..

+0

ver arriba, si fuera trivial, esperaría haber podido hacerlo sin ayuda. Sé que el problema se puede resolver de una manera diferente. –

+0

@Joanna: entendido, solo quería asegurarme de que no fuera un problema saber que OrderBy/ThenBy puede estar encadenado. Phoog tiene una gran solución a continuación. –

+0

Actualicé los resultados de algunas pruebas breves de rendimiento: la solución comparativa anónima es más lenta como se esperaba. Es bueno saberlo. –

0

No es exactamente una respuesta .. .pero demasiado tiempo para comentar: es difícil crear un comparador genérico sensible.

Si bien existe una relación de comparación bien establecida para los objetos por propiedad individual, no existen propiedades múltiples o incluso 2. Es decir. este es un problema muy común cuando intentas ordenar puntos en una superficie plana: solo 2 valores (x, y) pero no hay manera de decir (x1, y1) < (x2, y2) para que todos estén de acuerdo.

En la mayoría de los casos terminas ordenando por el atributo 1, que por el atributo 2, ... o asignando todos los atributos a un valor único (es decir, simplemente multiplicándolos a todos).Estos enfoques se expresan fácilmente sin necesidad de comparador genérico en LINQ:

  • pedido por atributos con encadenado OrdenarPor (attr1) .OrderBy (attr2) ....
  • pedido por métrica OrdenarPor (attr1 * attr2) (o cualquier otro Metric de sus objetos)
+1

Lo que me interesa básicamente es la implementación de esta cosa: http://code.google.com/p/edulinq/source/browse/src/Edulinq/ProjectionComparer.cs?r=0c583631b709679831c99df2646fc9adb781b2be. Supongo que si hago esto bien el resto simplemente funcionaría, pero necesitaría ayuda exactamente con esto. –

Cuestiones relacionadas